Etapa 1: carga de datos y preprocesamiento
El archivo que se adjunta consiste en un corpus de unas 7.000
noticias scrapeadas entre julio y septiembre de 2019 de los siguientes
medios de circulación nacional:
Télam La Nación Clarín Perfil Infobae MinutoUno Página 12
Constituye una muestra aleatoria del corpus construido por Florencia
Piñeyrúa para su tesina de grado “Procesamiento del lenguaje natural
aplicado al estudio de tópicos de noticias de seguridad en Argentina:
julio a septiembre 2019”. Una exposición más concentrada de sus
resultados puede encontrarse en el siguiente artículo.
El corpus contiene las siguientes variables:
id : identificador de cada documento url : link a la noticia original
fecha : fecha de publicación anio : año de publicación mes : mes de
publicación dia : dia de publicación medio : medio en el que fue
publicado orientacion: clasificación -provisoria- de los medios según su
línea editorial predominante (más conservador, más progresista, neutral)
titulo texto
Carga dataset
corpus_base <- read_csv("M5_corpus_medios.csv")
Rows: 7000 Columns: 10── Column specification ────────────────────────────────────────────────────
Delimiter: ","
chr (5): url, medio, orientacion, titulo, texto
dbl (4): id, anio, mes, dia
date (1): fecha
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# En este apartado vemos cuántas noticias aporta cada medio al corpus y calculamos la proporción sobre el total.
corpus_base %>%
group_by(medio) %>%
summarise(n=n()) %>%
mutate(
total = sum(n),
prop = n/total*100
) %>%
ungroup() %>%
select(medio, n, prop) %>%
arrange(desc(n))
Normalización y tokenización
corpus_tidy <- corpus_base %>%
mutate(texto= stringi::stri_trans_general(texto, "Latin-ASCII"),
titulo = stringi::stri_trans_general(titulo, "Latin-ASCII")) %>%
mutate(texto = str_replace_all(texto, '[[:digit:]]+', '')) %>%
unnest_tokens(word, texto, to_lower = TRUE)
# En este este código tomamos el corpus de texto "corpus_base", lo normalizamos convirtiendo el texto y el título a caracteres ASCII, eliminamos los dígitos del texto y finalmente los dividimos en tokens para su posterior análisis.
Eliminar stopwords
stop_words_1 <- read_csv('https://raw.githubusercontent.com/Alir3z4/stop-words/master/spanish.txt', col_names=FALSE) %>%
rename(word = X1) %>%
mutate(word = stringi::stri_trans_general(word, "Latin-ASCII"))
Rows: 608 Columns: 1── Column specification ────────────────────────────────────────────────────
Delimiter: ","
chr (1): X1
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# En este código leemos un archivo CSV de stop words en español ubicado en la URL especificada, renombramos la columna a "word" y luego transformamos las palabras en la columna para asegurarnos de que estén en formato ASCII. El resultado final es un dataframe llamado "stop_words_1" que contiene la lista de stopwords normalizadas.
stop_words_2 <- read_csv("z_stopwords.txt", col_names = FALSE) %>%
rename(word = X1) %>%
mutate(word=stringi::stri_trans_general(word, "Latin-ASCII"))
Rows: 732 Columns: 1── Column specification ────────────────────────────────────────────────────
Delimiter: ","
chr (1): X1
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Ídem pero el archivo CSV llamado "z_stopwords.txt" se carga desde la carpeta.
stop_words_full <- stop_words_1 %>%
bind_rows(stop_words_2) %>%
distinct()
# En este paso combinamos los dos dataframes de stopwords, eliminamos las filas duplicadas y guardamos el resultado en un nuevo dataframe llamado "stop_words_full". Este dataframe contiene la lista completa y única de stopwords normalizadas.
corpus_tidy <- corpus_tidy %>%
anti_join(stop_words_full)
Joining with `by = join_by(word)`
# En este código eliminamos las stopwords del corpus de texto "corpus_tidy" utilizando la lista de stopwords contenida en el dataframe "stop_words_full". Este paso en el procesamiento de texto es necesario para eliminar palabras que no aportan significado para el análisis posterior.
Corrección “años”
corpus_tidy <- corpus_tidy %>%
mutate(word = case_when(
word == 'ano' ~ 'anio',
word == 'anos' ~ 'anio',
TRUE ~ word
))
Etapa 2: Consignas
¿Cuáles son las palabras más utilizadas en cada uno de los medios?
¿Pueden verse diferencias? (Tener en cuenta las diferentes métricas
trabajadas en el curso: tf, tf-idf, etc.) Generar las visualizaciones
que considere más pertinentes para responder la pregunta.
Exploración palabras más frecuentes
Comenzamos explorando el corpus en formato tidy para identificar
rápidamente algunos de los términos más frecuentes por medio.
corpus_tidy %>%
group_by(word, medio) %>%
summarise(n=n()) %>%
arrange(desc(n))
`summarise()` has grouped output by 'word'. You can override using the `.groups` argument.
# Mediante este código agrupamos las palabras únicas en el corpus, contamos el número de ocurrencias de cada palabra y las ordenamos en función de su frecuencia de aparición, mostrando primero las palabras más frecuentes.
corpus_tidy %>%
filter(medio == 'infobae') %>%
group_by(word) %>%
summarise(n=n()) %>%
arrange(desc(n))
# Mirada rápida a las palabras más frecuentes de algunos medios
corpus_tidy %>%
group_by(medio, word) %>%
summarise(n=n()) %>%
arrange(desc(n)) %>%
pivot_wider(names_from = medio,
values_from = n)
`summarise()` has grouped output by 'medio'. You can override using the `.groups` argument.
# Este código calcula el recuento de ocurrencias de cada palabra en el corpus de texto agrupado según el medio de comunicación, y luego reorganiza estos resultados en un formato más ancho donde cada medio tiene sus recuentos de ocurrencias asociados para cada palabra.
word_counts_10 <- corpus_tidy %>%
group_by(medio, word) %>%
summarise(n=n()) %>%
slice_max(n, n = 10) %>%
ungroup()
`summarise()` has grouped output by 'medio'. You can override using the `.groups` argument.
# En este paso calculamos el recuento de ocurrencias de cada palabra en el corpus, agrupadas por la columna "medio", y almacena estos recuentos en un nuevo dataframe llamado "word_counts" para pasarle a la función de graficado.
word_counts_all_10 <- corpus_tidy %>%
group_by(word) %>%
summarise(n=n()) %>%
slice_max(n, n = 10) %>%
ungroup() %>%
mutate(medio = 'general')
# Aquí calculamos el recuento de ocurrencias de cada palabra en el corpus, sin agrupar por medio. Luego, seleccionamos las 10 palabras más comunes en todo el corpus. A estos resultados les agregamos la etiqueta "general" en la columna "medio" para poder comparar visualmente la distribución general con la distribución por medio
word_counts_top_10 <- word_counts_10 %>%
bind_rows(word_counts_all_10)
# Unifico en un dataset
A continuación creamos una función para automatizar todas las
visualizaciones relacionadas a las métricas del corpus.
crear_graf_words <- function(data) {
medios <- unique(data$medio)
graficos <- list()
for (medio_actual in medios) {
datos_medio <- data %>%
filter(medio == medio_actual) %>%
mutate(word = fct_reorder(word, n))
grafico <- ggplot(datos_medio, aes(n, word)) +
geom_col() +
geom_text(aes(label = n), position = position_stack(vjust = 0.5), family = "Courier", color = "white") +
labs(title = medio_actual,
x = "Frecuencia",
y = "Palabra") +
theme_classic()+
theme(plot.title = element_text(hjust= 0.5),
axis.title = element_blank(),
axis.ticks.x = element_blank(),
text = element_text(family = "Courier"))
graficos[[medio_actual]] <- grafico
}
wrap_plots(graficos)
}
# Esta función crea para cada medio un gráfico con el top 10 de palabras según cada métrica analizada
El eje x muestra las palabras y el eje y muestra la frecuencia de
cada palabra. El gráfico está segmentado en paneles por cada medio, y
las escalas del eje y se ajustan para cada panel individual.
crear_graf_words(word_counts_top_10)

# Este código genera una visualización de las palabras más comunes en el corpus, desglosadas por medio y en general. Las palabras más comunes se muestran en orden descendente de frecuencia en cada categoría.
Algunas observaciones preliminares:
Clarín e Infobae aportan cada uno el 22% de las noticias al
corpus. El peso de las palabras más importantes para estos medios en el
ranking general es alto.
Año parece una stopword porque es una palabra que presenta una
frecuencia alta intra e inter medios.
Crónica, LN e Infobae son los únicos que no incluyen nombres
propios entre las palabras más frecuentes.
En LN hay muchas referencias a redes sociales y acciones en ellas
(guardar, compartir). Las acciones y “fuente” podrían ser stopwords.
Incluso las menciones a RRSS podrían ser producto del scrapping y no del
contenido de las notas.
Macri aparece como el político más nombrado. Lógicamente, es un
resultado esperable si se tiene en cuenta que las notas relevadas
corresponden al último año de su gobierno.
Infobae y sobre todo Crónica parecen cubrir temas más generales
que los demás medios, al incluir palabras como “polícía”, “ciudad”,
“mujer”, “hombre”, “casa”, “personas”, “mundo”, “vida”. Incluso, la
frecuencia de la palabra “policía” en Crónica (#2 o #1 si se excluye el
término “anio”) podría indicar que la mayor parte de la cobertura de
este medio se orienta a la temática policial. De todas formas, en el top
10 también aparecen palabras como “gobierno” y “presidente”.
La palabra “frente” aparece con frecuencia en varios medios, lo
que nos inclina a corroborar si refiere a frentes electorales o
simplemente a un adverbio de lugar y, en este caso, debería considerarse
como stopword.
A modo de síntesis, podría advertirse que las palabras con mayor
frecuencia de aparición en la totalidad de los medios se corresponden
con la temática política. Es probable que esta vinculación sea un
desprendimiento del escenario electoral que signó el período bajo
análisis.
Term Frequency - TF:
A modo de práctica generamos el cálculo de term frequency manualmente
para visualizar la distribución de términos según su frecuencia respecto
al total de términos, aperturado por medio.
# La "term frequency" (frecuencia de término) es una métrica para evaluar la importancia de una palabra en un documento o corpus de documentos, a partir de la medición de la frecuencia de su aparición en comparación con el número total de palabras.
words_tidy <- corpus_tidy %>%
group_by(medio, word) %>% # en función de la consigna, hacemos el conteo de términos por medio
summarise(n=n()) %>%
arrange(desc(n))
`summarise()` has grouped output by 'medio'. You can override using the `.groups` argument.
total_words <- words_tidy %>%
group_by(medio) %>%
summarize(total = sum(n))
words_tidy <- words_tidy %>%
left_join(total_words) %>%
ungroup() %>%
arrange(desc(n))
Joining with `by = join_by(medio)`
# En este código contamos la frecuencia de cada palabra en el corpus del medio y también el total de palabras en el medio. Con estos datos podemos calcular a continuación la importancia de cada término en el medio.
PRUEBITASSS
words_tidy_test <- corpus_tidy %>%
group_by(id, word, medio) %>%
summarise(n=n()) %>%
arrange(desc(n))
`summarise()` has grouped output by 'id', 'word'. You can override using the `.groups` argument.
total_words_test <- words_tidy_test %>%
group_by(id) %>%
summarize(total = sum(n))
words_tidy_test <- words_tidy_test %>%
left_join(total_words_test) %>%
ungroup() %>%
arrange(desc(n))
Joining with `by = join_by(id)`
t <- words_tidy_test %>% mutate(tf = n/total) %>%
ggplot(aes(tf)) +
geom_histogram(show.legend = FALSE) +
coord_flip()+
#xlim(NA, 0.0002) +
facet_wrap(~medio) +
theme_classic()+
theme(plot.title = element_text(hjust= 0.5),
axis.title = element_blank(),
axis.ticks.x = element_blank(),
text = element_text(family = "Courier"))
En esta visualización podemos interactuar para identificar la
cantidad de términos por tf:
tf_viz <- words_tidy %>% mutate(tf = n/total) %>%
ggplot(aes(tf)) +
geom_histogram(show.legend = FALSE) +
coord_flip()+
xlim(NA, 0.0002) +
facet_wrap(~medio) +
theme_classic()+
theme(plot.title = element_text(hjust= 0.5),
axis.title = element_blank(),
axis.ticks.x = element_blank(),
text = element_text(family = "Courier"))
ggplotly(tf_viz)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.Warning: Removed 6859 rows containing non-finite values (`stat_bin()`).
Vemos que para los medios más masivos se cumple la Ley de Zipf: pocas
palabras ocurren muchas veces. Los corpus de Crónica, Minuto Uno y Télam
tienen menos términos que los demás y la tf tiene un rango menor (uso de
palabras mejor distribuido).
Term Frequency - Inverse Document Frequency (TF-IDF)
En esta etapa complementamos el análisis con las métricas de idf y
tf_idf, para obtener una medida más completa de la importancia de los
términos en el corpus de documentos.
tf_idf <- words_tidy %>%
bind_tf_idf(word, medio, n)
tf_idf %>% select(-total) %>%
arrange(desc(tf_idf))
# En este código calculamos las métricas tf, idf y tf_idf para todos los términos del corpus
Creamos tres dataset, uno por métrica, con el top ten de términos
para pasarle a la función de visualización.
tf_10 <- tf_idf %>%
group_by(medio) %>%
slice_max(tf, n = 10) %>%
select(medio, word, tf) %>%
rename(n = tf) %>%
mutate(n = log(n + 1) * 100) %>%
mutate(n = round(n, 4))
idf_10 <- tf_idf %>%
group_by(medio) %>%
arrange(desc(idf)) %>%
slice_head(n = 10) %>%
select(medio, word, idf) %>%
rename(n = idf) %>%
mutate(n = log(n + 1) * 100) %>%
mutate(n = round(n, 4))
tf_idf_10 <- tf_idf %>%
group_by(medio) %>%
arrange(desc(idf)) %>%
slice_head(n = 10) %>%
select(medio, word, tf_idf) %>%
rename(n = tf_idf) %>%
mutate(n = log(n + 1) * 100) %>%
mutate(n = round(n, 4))
Pasamos cada uno de los top ten de términos por la función de
graficado para visualizar los términos más importantes según cada
métrica.
crear_graf_words(tf_10)

crear_graf_words(idf_10)

crear_graf_words(tf_idf_10)

Análisis preliminar de TF-IDF:
En primer lugar identificamos la necesidad de agregar palabras al
listado de stopwords. Entendemos que no aportan información sobre el
contenido u orientación de las noticias y a su vez son únicos para
algunos medios y eso genera un alto tf_idf.
Ejemplos: jpe, ap, emj, cronica.com.ar, fvazquez, cronicavirales, hd,
pemex, gt, afv, jpg, minutouno.com, ambito.com, ivanovich, loading,
paginai, protected, lxs, email, r.c,cp, fel,l.l,d.s, ea, f.f, f.d.s, fh,
a.g, pct, telam.la, nacional.el
Podemos identificar “firmas” de periodistas (nombres de usuario o
siglas) que tampoco son informativas sobre el contenido del
corpus.
Etapa 1.2: Ampliación stopwords y reprocesamiento de la base
El análisis realizado nos permitió encontrar numerosas palabras
incluidas en el corpus que creemos son producto del scrapping. Previo a
la modelización para identificar tópicos creemos pertinente ampliar el
listado de stopwords y repetir el preprocesamiento de los datos.
Para ampliar el listado de stopwords partimos del listado de palabras
con sus métricas de tf e idf.
# Mediante el siguiente código eliminamos palabras que incluyen puntos y parecen producto de scrapping web.
nuevas_stopwords_1 <- tf_idf %>%
select(word) %>%
filter(grepl('\\.', word)) %>%
pull(word)
# En este paso eliminamos palabras de dos caracteres, salvo contadas excepciones que tienen sentido.
nuevas_stopwords_2 <- tf_idf %>%
select(word) %>%
filter(str_length(word) == 2 & !(word %in% c('pj', 'fe', 'dt', 'tv', 'km', 'cv', 'dr', 'it', 'dj', 'ux'))) %>%
pull(word)
# Este código elimina palabras de tres caracteres. Parece haber más siglas y palabras cortas que tienen sentido pero, dada la gran cantidad de stopwords de 3 caracteres, hay motivos para incluir este paso.
nuevas_stopwords_3 <- tf_idf %>%
select(word) %>%
filter(str_length(word) == 3 & !(word %in% c('san', 'mil', 'ley', 'afp', 'sur', 'fmi', 'usd', 'rio', 'gol', 'paz', 'mar', 'oro', 'red', 'luz', 'voz' , 'rol', 'sol', 'gas', 'pie', 'par', 'pro', 'via', 'onu', 'ypf', 'iva', 'afa', 'pbi', 'bar', 'cfk', 'eje', 'rey', 'atp', 'don', 'fbi', 'gay', 'psg', 'uba', 'ucr', 'ceo', 'ong', 'fed', 'ojo', 'dea', 'uva', 'cgt', 'ufi', 'app', 'gil', 'vih', 'nba', 'bbc', 'evo', 'hip', 'hop', 'fox', 'nbc', 'rap', 'adn', 'ala', 'eva', 'pan', 'zen', 'afv', 'cck', 'eco', 'oca', 'tio', 'cnn', 'cia', "dni", "uia", "fdt", "uif", "hbo", "mls", "oea", "mep", "gnc", "auh", "che", "oil", "gen", "agn", "fpv", "lam", "pib", "cne","duo", "vox", "dow", "uca", "dnu", "pez", "pfa", "pdt", "fda", "fci", "oms", "psa", "feo", "cta", "ego",
"faa", "ute", "ate", "lio", "fpt", "suv", "gba", "izq", "aro", "smn", "tnt", "uco", "ipc", "saa", "tmz", "ccl", "gel", "vip", "esi", "res", "kun", "tsj", "afi", "pts", "cnh", "ajo", "acv", "bmw", "bus", "gps", "ile", "ios", "unc", "zoo", "jup", "tos", "unl", "upl", "zeo", "ave", "mte", "mpn", "apn", "mao", "pba", "sms", "cnv", "mdz", "fol", "iso"))) %>%
pull(word)
nuevas_stopwords <- data.frame(word = nuevas_stopwords_1) %>%
bind_rows(data.frame(word = nuevas_stopwords_2)) %>%
bind_rows(data.frame(word = nuevas_stopwords_3))
stop_words_full_v2 <- stop_words_full %>%
bind_rows(tibble(word = c('embed', 'anio', 'ano', 'anos', 'gusta', 'twitter', 'facebook', 'comentar', 'fuente', 'whatsapp', 'guardar', 'compartir', 'mail', 'loading', 'email', 'paginai', 'eltrece', 'infobae', 'http', 'https', 'attribute', 'find_all', 'nonetype', 'object', 'read' ))) %>%
bind_rows(nuevas_stopwords)
A partir de la nueva lista de stopwords, recreamos el corpus en
formato tidy y también el dataframe en formato medio/término/n para
continuar con el análisis de métricas.
corpus_tidy_v2 <- corpus_tidy %>%
anti_join(stop_words_full_v2)
Joining with `by = join_by(word)`
words_tidy_v2 <- corpus_tidy_v2 %>%
group_by(medio, word) %>%
summarise(n=n())
`summarise()` has grouped output by 'medio'. You can override using the `.groups` argument.
total_words_v2 <- words_tidy_v2 %>%
group_by(medio) %>%
summarize(total = sum(n))
words_tidy_v2 <- words_tidy_v2 %>%
left_join(total_words_v2) %>%
ungroup() %>%
arrange(desc(n))
Joining with `by = join_by(medio)`
Revisión de métricas
Creamos el gráfico con la distribución de términos según su tf para
identificar variaciones respecto a la v1.
tf_viz_v2 <- words_tidy_v2 %>% mutate(tf = n/total) %>%
ggplot(aes(tf)) +
geom_histogram(show.legend = FALSE) +
coord_flip()+
xlim(NA, 0.0002) +
facet_wrap(~medio) +
theme_classic()+
theme(plot.title = element_text(hjust= 0.5),
axis.title = element_blank(),
axis.ticks.x = element_blank(),
text = element_text(family = "Courier"))
ggplotly(tf_viz_v2)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.Warning: Removed 6945 rows containing non-finite values (`stat_bin()`).
tf_idf_v2 <- words_tidy_v2 %>%
bind_tf_idf(word, medio, n)
Creamos los dataset necesarios para visualizar los principales
términos por métrica y por medio, buscando diferencias respecto a la
v1.
tf_10_v2 <- tf_idf_v2 %>%
group_by(medio) %>%
arrange(desc(tf)) %>%
slice_head(n = 10) %>%
select(medio, word, tf) %>%
rename(n = tf) %>%
mutate(n = log(n + 1) * 100) %>%
mutate(n = round(n, 4))
idf_10_v2 <- tf_idf_v2 %>%
group_by(medio) %>%
arrange(desc(idf)) %>%
slice_head(n = 10) %>%
select(medio, word, idf) %>%
rename(n = idf) %>%
mutate(n = log(n + 1) * 100) %>%
mutate(n = round(n, 4))
tf_idf_10_v2 <- tf_idf_v2 %>%
group_by(medio) %>%
arrange(desc(idf)) %>%
slice_head(n = 10) %>%
select(medio, word, tf_idf) %>%
rename(n = tf_idf) %>%
mutate(n = log(n + 1) * 100) %>%
mutate(n = round(n, 4))
crear_graf_words(tf_10_v2)

crear_graf_words(tf_10)

crear_graf_words(idf_10_v2)

crear_graf_words(tf_idf_10_v2)

Análisis final de TF-IDF:
En todos los medios, excepto en La Nación que no la incluía,
logró eliminarse la palabra “anio” que figuraba en el primer lugar en
términos de frecuencia. Además, en los casos de Crónica y Minuto 1, se
eliminó otra palabra adicional incorporando, de esa manera, dos nuevas
al top diez.
El caso de La Nación ya se muestra normalizado, sin las stopwords
que aparecían producto del scraping.
| ## Modelado de tópicos: LDA |
| ¿Cuáles son los tópicos principales en el corpus?
¿Pueden evidenciar diferencias en cada uno de los medios? Explicar qué
método se utilizó para responder la pregunta, cuáles son los supuestos
del mismo. Generar las visualizaciones más adecuadas para responder a
las preguntas. |
| Para implementar un modelado de temas con LDA
necesitamos construir una matriz DTM. |
| # A PARTIR DE ACÁ dejo las dos pruebas para que puedas
comparar. Si usamos la matriz término-frecuencia por MEDIO, la detección
de tópicos da cualquier cosa. Si usamos la matriz término-frecuencia por
NOTICIA el resultado tiene más sentido. No estoy segura como se explica,
quizás podemos encontrar alguna justificación. |
| –> Nos quedamos con la versión 2 (por id) |
|
|
|
| ```r disc_dtm_1 <- words_tidy_v2 %>%
cast_dtm(medio, word, n) |
| ``` |
|
|
|
|
|
|
r para_disc_dtm <- corpus_tidy_v2 %>% group_by(id, word) %>% summarise(n=n()) |
|
|
`summarise()` has grouped output by 'id'. You can override using the `.groups` argument. |
|
|
| ```r # creo un conteo de palabras por noticia, no por
medio |
| disc_dtm_2 <- para_disc_dtm %>% cast_dtm(id,
word, n) |
| ``` |
|
|
|
|
|
|
| ```r lda_orig_5 <- LDA(disc_dtm_1, k = 5, control =
list(seed = 1234)) # k es el número de tópicos a identificar |
| ap_topics <- tidy(lda_orig_5, matrix = “beta”) # la
función tidy convierte el modelo a un tópico-término por fila |
| ap_topics %>% mutate(beta = round(100*beta,6))
``` |
|
|
|
|
|
| ```r |
| ap_top_terms <- ap_topics %>% group_by(topic)
%>% slice_max(beta, n = 15) %>% ungroup() %>% arrange(topic,
-beta) |
| ap_top_terms %>% mutate(term = reorder_within(term,
beta, topic)) %>% ggplot(aes(beta, term, fill = factor(topic))) +
geom_col(show.legend = FALSE) + facet_wrap(~ topic, scales=‘free_y’) +
scale_y_reordered() + theme_minimal() ``` |
|
|
 |
|
|
|
|
|
|
| ```r lda_v2_5 <- LDA(disc_dtm_2, k = 5, control =
list(seed = 555)) # k es el número de tópicos a identificar |
| ap_topics_5 <- tidy(lda_v2_5, matrix = “beta”) # la
función tidy convierte el modelo a un tópico-término por fila |
| ap_topics_5 %>% mutate(beta = round(100*beta,6))
``` |
|
|
|
|
|
| ```r |
| ap_top_terms_5 <- ap_topics_5 %>% group_by(topic)
%>% slice_max(beta, n = 15) %>% ungroup() %>% arrange(topic,
-beta) |
| ap_top_terms_5 %>% mutate(term =
reorder_within(term, beta, topic)) %>% ggplot(aes(beta, term, fill =
factor(topic))) + geom_col(show.legend = FALSE) + facet_wrap(~ topic,
scales=‘free_y’) + scale_y_reordered() + theme_minimal() ``` |
|
|
 |
|
|
|
| Pareciera que los cinco tópicos arrojados como
resultado tienen un sentido definido: |
| 1- Política Nacional/ Elecciones 2- Deportes 3-
Sociedad 4- Política Internacinal 5- Economía |
| A continuación, hacemos una nueva prueba para explorar
si es posible identificar una mayor cantidad de tópicos (k=10): |
|
|
|
| ```r lda_v2_10 <- LDA(disc_dtm_2, k = 10, control =
list(seed = 1010)) # k es el número de tópicos a identificar |
| ap_topics_10 <- tidy(lda_v2_10, matrix = “beta”) #
la función tidy convierte el modelo a un tópico-término por fila |
| ap_topics_10 %>% mutate(beta = round(100*beta,6))
``` |
|
|
|
|
|
| ```r |
| ap_top_terms_10 <- ap_topics_10 %>%
group_by(topic) %>% slice_max(beta, n = 15) %>% ungroup() %>%
arrange(topic, -beta) |
| ap_top_terms_10 %>% mutate(term =
reorder_within(term, beta, topic)) %>% ggplot(aes(beta, term, fill =
factor(topic))) + geom_col(show.legend = FALSE) + facet_wrap(~ topic,
scales=‘free_y’) + scale_y_reordered() + theme_minimal() ``` |
|
|
 |
|
|
|
| Con esta prueba, que duplicó la cantidad de tópicos,
comenzamos a registrar conjuntos que no tienen un sentido tan definido,
catalogados provisoriamente como tópicos de “Sociedad” (1, 3 y 6). A su
vez, en dos casos es posible reconocer una superposición temática (4 y
8). |
| 1- Sociedad 2- Política Nacional/ Elecciones 3-
Sociedad (¿Vida Familiar?) 4- Política Internacional 5- Policiales 6-
Sociedad 7- Deporte 8- Política Internacional 9- Economía 10- Cultura/
Cine y Series |
| En esa dirección, avanzamos en una nueva prueba que
propone reducir levemente la cantidad de tópicos (k=8): |
|
|
|
| ```r lda_v2_8 <- LDA(disc_dtm_2, k = 8, control =
list(seed = 888)) # k es el número de tópicos a identificar |
| ap_topics_8 <- tidy(lda_v2_8, matrix = “beta”) # la
función tidy convierte el modelo a un tópico-término por fila |
| ap_topics_8 %>% mutate(beta = round(100*beta,6))
``` |
|
|
|
|
|
| ```r |
| ap_top_terms_8 <- ap_topics_8 %>% group_by(topic)
%>% slice_max(beta, n = 15) %>% ungroup() %>% arrange(topic,
-beta) |
| ap_top_terms_8 %>% mutate(term =
reorder_within(term, beta, topic)) %>% ggplot(aes(beta, term, fill =
factor(topic))) + geom_col(show.legend = FALSE) + facet_wrap(~ topic,
scales=‘free_y’) + scale_y_reordered() + theme_minimal() ``` |
|
|
 |
|
|
|
| En comparación con la prueba anterior, también
detectamos tres tópicos con un sentido difuso (1, 3 y 7), mientras que
desaparecieron tanto la repetición del tópico “Política Internacional”
como el tópico “Cultura/ Cine y Series”. |
| 1- Sociedad 2- Economía 3- Sociedad 4- Policiales 5-
Política Internacional 6- Deportes 7- Sociedad (¿Y Cultura?) 8- Política
Nacional/ Elecciones |
| Teniendo en cuenta las tres pruebas realizadas con el
modelo LDA, entendemos que el mejor modelo es el que plantea un K=5. ES
DECIR, TODO LO QUE SIGUE ESTÁ PLANTEADO DESDE EL MODELO DE K=5. |
| # DUDA: en el ejemplo que aparece en la notebook, hay
dos temas que no tienen un sentido definido y por eso “Esto parece un
primer indicador de que deberíamos considerar la posibilidad de utilizar
un número de tópicos más elevado”. En nuestro caso lo estoy pensando al
revés (menos k, mayor definición de tópicos). |
| La visualización correspondiente a k=5 muestra que
algunas palabras como “argentina”, “personas” son comunes a más de un
tema. Es decir, que los tópicos identificados tienen cierta
superposición en términos de palabras. Como alternativa, podríamos
considerar los términos que tuvieran la mayor diferencia en β entre el
tema 1 y el tema 5 (que son dos de los que mejor podemos interpretar).
NO ME QUEDA DEL TODO CLARO, PERO ME PARECE QUE ESTE PASO PERMITE
CORROBORAR QUE DOS TÓPICOS SEAN LO SUFICIENTEMENTE DIFERENTES. |
|
|
|
| ```r beta_wide <- ap_topics_5 %>% mutate(topic =
paste0(“topic”, topic)) %>% pivot_wider(names_from = topic,
values_from = beta) %>% filter(topic1 > .002 | topic5 > .002)
%>% mutate(log_ratio1_5 = log2(topic5 / topic1)) |
| beta_wide ``` |
|
|
|
|
|
r NA |
|
|
|
|
r beta_wide %>% ggplot(aes(x=reorder(term,log_ratio1_5) , y=log_ratio1_5)) + geom_col() + coord_flip() + labs(x='Término', y='Log2 ratio topic5/topic1') + theme_minimal() |
|
|
 |
|
|
|
| Palabras como “vidal”, “justicia” o “kirchner”
caracterizan al tópico 1, mientras que “productos”, “inflación” o
“mercado” representan al tópico 5. Esto contribuye a confirmar que se
trata de dos tópicos diferenciados. |
| Composición de tópicos por documento: Si antes
estimamos cada tema como una mezcla de palabras, ahora usamos LDA
también para modelar cada documento como una mezcla de temas. |
|
|
|
r doc_2_topics <- tidy(lda_v2_5, matrix = "gamma") doc_2_topics %>% mutate(gamma = round(gamma, 5), document = as.integer(document)) %>% arrange(document, desc(gamma)) |
|
|
|
|
|
|
| Cada uno de estos valores es una proporción estimada de
palabras de ese documento que se generan a partir de ese tema. |
|
|
|
r doc_2_topics %>% filter(topic == 1 |topic == 2 |topic == 3 |topic == 4 | topic == 5) %>% mutate(gamma = round(gamma, 5)) |
|
|
|
|
|
|
| En particular, si observamos el documento 94, la
probabilidad gamma asociada al tema 1 es 0.99878, lo que significa que
el modelo estima que alrededor del 99% de las palabras en el documento
94 se generaron a partir del tema 1. Por lo tanto, podemos concluir que
el tema 1 es altamente relevante para el documento 94 según el modelo.
Por otro lado, para el documento 43, la probabilidad gamma asociada al
tema 1 es de 0.00018, lo que indica que menos del 1% de las palabras del
documento 43 se generan a partir del tema 1. En esa dirección, podemos
concluir que el tema 1 es poco relevante para el documento 43 según el
modelo. |
|
|
|
r corpus_tidy_v2%>% filter(id==94) %>% group_by(id, word) %>% summarise(n=n()) %>% select(word, n) %>% arrange(desc(n)) |
|
|
`summarise()` has grouped output by 'id'. You can override using the `.groups` argument.Adding missing grouping variables: `id` |
|
|
|
|
|
|
| Se ve como en esta noticia parecen predominar palabras
del tópico 1 (“nacional”, “elecciones”). Veamos el texto completo de
este documento: |
|
|
|
r corpus_base %>% filter(id==94) %>% select(texto) %>% pull() |
|
|
[1] "El candidato a gobernador por Cambia Mendoza, Rodolfo Suárez, dijo este domingo que separar la elección provincial de la nacional beneficia sus chances y aseguró que trabajará para que Juntos por el Cambio “siga gobernando el país después de diciembre”.\n\n“Esta es una elección provincial. Hubo una elección nacional que fue adversa al Gobierno nacional y no queríamos que eso influya en Mendoza”, reconoció Suárez.\n\n\"Tenemos diferencias con temas tarifas y economía con Macri y las hemos planteado, pero aspiramos a la continuidad de Juntos por el Cambio y vamos a trabajar fuerte por eso desde mañana\" le dijo @rodysuarez a @cronica — Gabriel Calisto (@GCalisto) September 29, 2019\n\nLuego de votar en la Escuela Arístides Villanueva del centro de la capital provincial, el postulante comentó que lo llamó esta mañana el presidente Mauricio Macri para desearle suerte, así como también lo hicieron “dirigentes del radicalismo”.\n\nTambién adelantó que si gana las elecciones, no hay invitados del Gobierno nacional para los festejos.\n\nNo obstante, mencionó su deseo de que “Juntos por el Cambio siga gobernando el país después de diciembre” y afirmó que desde Mendoza van a “trabajar mucho para que (el actual gobernador radical, Alfredo) Cornejo gane las elecciones como diputado nacional”.\n\nHoy, votamos #AfavorDeMendoza. Para que la provincia siga creciendo y los mendocinos vivan cada vez mejor.\n\n\n\n¡Feliz de vivir una nueva jornada democrática en Mendoza! #RodyGobernador pic.twitter.com/GW54nxE0Xy — Rodolfo Suarez (@rodysuarez) September 29, 2019\n\nRespecto a la relación dentro de la alianza que gobierna a nivel nacional, Suárez mencionó que “el radicalismo marcó diferencias, pero siempre dentro del marco en que hay que hacerlo”.“Ojalá que Mendoza pueda colaborar en que tome impulso ese anhelo del Presidente de lograr la reelección”, enfatizó.\n\n@rodysuarez pidió hacer la fila antes de votar. pic.twitter.com/vdked8YD0M — Gabriel Calisto (@GCalisto) September 29, 2019\n\n“Tuvimos gobiernos como el de (el peronista Francisco) Pérez con Cristina (Fernández de Kirchner en el gobierno) y a los mendocinos nos fue muy mal. Tuvimos casos como el de Julio Cobos (cuando Néstor Kirchner era presidente) en que a los mendocinos nos fue bien”, recordó.\n\nEn ese sentido, agregó que “pasadas las elecciones\", es necesario olvidarse a \"qué partido pertenece y trabajar por los argentinos\" y agregó: \"Es lo que la gente espera de nosotros y lo que corresponde en un marco democrático”.\n\nLEÉ TAMBIÉN: Los mendocinos van a las urnas\n\nSuárez se mostró confiado en obtener un triunfo en las elecciones de este domingo al mencionar el \"afecto\" que recibió de los mendocinos y las mendocinas después de recorrer la provincia \"más de seis veces”\n\n“Eso nos hace tener muchas esperanzas de que Mendoza va a seguir por esta senda de los buenos gobiernos como el de Alfredo Cornejo que estuvo lejos del populismo y de regalar todo y más cerca de administrar bien el Estado y de prestar buenos servicios”, sentenció." |
|
|
|
| La noticia (id=94) habla de las elecciones y, en
particular, de las elecciones en la provincia de Mendoza. |
|
|
|
r doc_2_topics %>% rename(id = document) %>% # tenemos que renombrar la columna para que pueda hacerse el join mutate(id = as.integer(id)) %>% left_join(corpus_base %>% select(id, medio) %>% unique()) %>% group_by(medio, topic) %>% summarise(mean = mean(gamma)*100) %>% ggplot() + geom_col(aes(x=topic, y=mean, fill=medio), position='dodge') + theme_minimal() |
|
|
Joining with `by = join_by(id)``summarise()` has grouped output by 'medio'. You can override using the `.groups` argument. |
|
|
 |
|
|
|
| Resultados (interpretación a desarrollar) Es posible
reconocer diferencias en la prevalencia de los tópicos para cada uno de
los medios, con expceción del tópico “Economía” que muestra una
prevalencia similar en cuatro medios. |
| 1- Política Nacional/ Elecciones = Télam 2- Deportes =
Crónica 3- Sociedad = La Nación 4- Política Internacinal = Infobae 5-
Economía = Télam, Página 12, Perfil y Clarín |
| ## Modelado de tópicos: STM |
| Para implementar un modelado de temas con STM
necesitamos construir una matriz DFM. |
|
|
|
| ```r disc_dfm <- words_tidy_v2 %>%
cast_dfm(medio, word, n) |
| disc_dfm ``` |
|
|
|
A continuación, seleccionar las noticias vinculadas a algún tópico
relevante (por ejemplo, “Elecciones”) y construir un clasificador para
predecir la orientación del diario. Utilizar alguno de los modelos de
clasificación vistos a lo largo de al Diplomatura (regresión logística,
random forest, etc.). Utilizar como features el “Spanish Billion Word
Corpus and Embeddings”, analizado en clase (pueden descargar el
embedding en formato .bin del link). ¿Qué resultados arroja el modelo?
¿Es posible mediante el texto de las noticias conocer la línea editorial
del diario? Generar las visualizaciones y tablas correspondientes para
una correcta evaluación del modelo.
LS0tCnRpdGxlOiAiVHJhYmFqbyBGaW5hbCBEaXBsb21hQ1NDIC0gT3BjacOzbiAyIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yX29wdGlvbnM6IAogIG1hcmtkb3duOiAKICAgIHdyYXA6IDcyCi0tLQoKIyMgQ2FyZ2EgZGUgbGlicmVyw61hcyBhIHV0aWxpemFyCgpgYGB7ciBsaWJyYXJ5fQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeSh0bSkKbGlicmFyeSh0ZXh0c3RlbSkKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkod29yZGNsb3VkKSAKbGlicmFyeShwbG90bHkpCmxpYnJhcnkodG9waWNtb2RlbHMpCmxpYnJhcnkodGljdG9jKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkocmVzaGFwZTIpCmBgYAoKIyMgRXRhcGEgMTogY2FyZ2EgZGUgZGF0b3MgeSBwcmVwcm9jZXNhbWllbnRvCgpFbCBhcmNoaXZvIHF1ZSBzZSBhZGp1bnRhIGNvbnNpc3RlIGVuIHVuIGNvcnB1cyBkZSB1bmFzIDcuMDAwIG5vdGljaWFzIHNjcmFwZWFkYXMgZW50cmUganVsaW8geSBzZXB0aWVtYnJlIGRlIDIwMTkgZGUgbG9zIHNpZ3VpZW50ZXMgbWVkaW9zIGRlIGNpcmN1bGFjacOzbiBuYWNpb25hbDoKClTDqWxhbSBMYSBOYWNpw7NuIENsYXLDrW4gUGVyZmlsIEluZm9iYWUgTWludXRvVW5vIFDDoWdpbmEgMTIKCkNvbnN0aXR1eWUgdW5hIG11ZXN0cmEgYWxlYXRvcmlhIGRlbCBjb3JwdXMgY29uc3RydWlkbyBwb3IgRmxvcmVuY2lhIFBpw7FleXLDumEgcGFyYSBzdSB0ZXNpbmEgZGUgZ3JhZG8g4oCcUHJvY2VzYW1pZW50byBkZWwgbGVuZ3VhamUgbmF0dXJhbCBhcGxpY2FkbyBhbCBlc3R1ZGlvIGRlIHTDs3BpY29zIGRlIG5vdGljaWFzIGRlIHNlZ3VyaWRhZCBlbiBBcmdlbnRpbmE6IGp1bGlvIGEgc2VwdGllbWJyZSAyMDE54oCdLiBVbmEgZXhwb3NpY2nDs24gbcOhcyBjb25jZW50cmFkYSBkZSBzdXMgcmVzdWx0YWRvcyBwdWVkZSBlbmNvbnRyYXJzZSBlbiBlbCBzaWd1aWVudGUgYXJ0w61jdWxvLgoKRWwgY29ycHVzIGNvbnRpZW5lIGxhcyBzaWd1aWVudGVzIHZhcmlhYmxlczoKCmlkIDogaWRlbnRpZmljYWRvciBkZSBjYWRhIGRvY3VtZW50bwp1cmwgOiBsaW5rIGEgbGEgbm90aWNpYSBvcmlnaW5hbApmZWNoYSA6IGZlY2hhIGRlIHB1YmxpY2FjacOzbgphbmlvIDogYcOxbyBkZSBwdWJsaWNhY2nDs24KbWVzIDogbWVzIGRlIHB1YmxpY2FjacOzbgpkaWEgOiBkaWEgZGUgcHVibGljYWNpw7NuCm1lZGlvIDogbWVkaW8gZW4gZWwgcXVlIGZ1ZSBwdWJsaWNhZG8Kb3JpZW50YWNpb246IGNsYXNpZmljYWNpw7NuIC1wcm92aXNvcmlhLSBkZSBsb3MgbWVkaW9zIHNlZ8O6biBzdSBsw61uZWEKZWRpdG9yaWFsIHByZWRvbWluYW50ZSAobcOhcyBjb25zZXJ2YWRvciwgbcOhcyBwcm9ncmVzaXN0YSwgbmV1dHJhbCkKdGl0dWxvCnRleHRvCgojIyMgQ2FyZ2EgZGF0YXNldAoKYGBge3IgcmVhZGNzdn0KY29ycHVzX2Jhc2UgPC0gcmVhZF9jc3YoIk01X2NvcnB1c19tZWRpb3MuY3N2IikKYGBgCgpgYGB7ciBhbmFsaXNpc19iYXNlfQojIEVuIGVzdGUgYXBhcnRhZG8gdmVtb3MgY3XDoW50YXMgbm90aWNpYXMgYXBvcnRhIGNhZGEgbWVkaW8gYWwgY29ycHVzIHkgY2FsY3VsYW1vcyBsYSBwcm9wb3JjacOzbiBzb2JyZSBlbCB0b3RhbC4KCiAgICBjb3JwdXNfYmFzZSAlPiUKICAgICAgICBncm91cF9ieShtZWRpbykgJT4lCiAgICAgICAgc3VtbWFyaXNlKG49bigpKSAlPiUKICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgICAgICB0b3RhbCA9IHN1bShuKSwKICAgICAgICAgICAgICAgIHByb3AgPSBuL3RvdGFsKjEwMAogICAgICAgICAgICAgICAgKSAlPiUKICAgICAgICB1bmdyb3VwKCkgJT4lCiAgICAgICAgc2VsZWN0KG1lZGlvLCBuLCBwcm9wKSAlPiUgCiAgICAgICAgYXJyYW5nZShkZXNjKG4pKQpgYGAKCiMjIyBOb3JtYWxpemFjacOzbiB5IHRva2VuaXphY2nDs24KCmBgYHtyIHRva2Vuc30KY29ycHVzX3RpZHkgPC0gY29ycHVzX2Jhc2UgJT4lIAogICAgbXV0YXRlKHRleHRvPSBzdHJpbmdpOjpzdHJpX3RyYW5zX2dlbmVyYWwodGV4dG8sICJMYXRpbi1BU0NJSSIpLAogICAgICAgICB0aXR1bG8gPSBzdHJpbmdpOjpzdHJpX3RyYW5zX2dlbmVyYWwodGl0dWxvLCAiTGF0aW4tQVNDSUkiKSkgJT4lIAogICAgbXV0YXRlKHRleHRvID0gc3RyX3JlcGxhY2VfYWxsKHRleHRvLCAnW1s6ZGlnaXQ6XV0rJywgJycpKSAlPiUgCiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHRvLCB0b19sb3dlciA9IFRSVUUpCgojIEVuIGVzdGUgZXN0ZSBjw7NkaWdvIHRvbWFtb3MgZWwgY29ycHVzIGRlIHRleHRvICJjb3JwdXNfYmFzZSIsIGxvIG5vcm1hbGl6YW1vcyBjb252aXJ0aWVuZG8gZWwgdGV4dG8geSBlbCB0w610dWxvIGEgY2FyYWN0ZXJlcyBBU0NJSSwgZWxpbWluYW1vcyBsb3MgZMOtZ2l0b3MgZGVsIHRleHRvIHkgZmluYWxtZW50ZSBsb3MgZGl2aWRpbW9zIGVuIHRva2VucyBwYXJhIHN1IHBvc3RlcmlvciBhbsOhbGlzaXMuCmBgYAoKIyMjIEVsaW1pbmFyIHN0b3B3b3JkcwoKYGBge3Igc3RvcHdvcmRzfQpzdG9wX3dvcmRzXzEgPC0gcmVhZF9jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9BbGlyM3o0L3N0b3Atd29yZHMvbWFzdGVyL3NwYW5pc2gudHh0JywgY29sX25hbWVzPUZBTFNFKSAlPiUKICAgICAgICByZW5hbWUod29yZCA9IFgxKSAlPiUKICAgICAgICBtdXRhdGUod29yZCA9IHN0cmluZ2k6OnN0cmlfdHJhbnNfZ2VuZXJhbCh3b3JkLCAiTGF0aW4tQVNDSUkiKSkKCiMgRW4gZXN0ZSBjw7NkaWdvIGxlZW1vcyB1biBhcmNoaXZvIENTViBkZSBzdG9wIHdvcmRzIGVuIGVzcGHDsW9sIHViaWNhZG8gZW4gbGEgVVJMIGVzcGVjaWZpY2FkYSwgcmVub21icmFtb3MgbGEgY29sdW1uYSBhICJ3b3JkIiB5IGx1ZWdvIHRyYW5zZm9ybWFtb3MgbGFzIHBhbGFicmFzIGVuIGxhIGNvbHVtbmEgcGFyYSBhc2VndXJhcm5vcyBkZSBxdWUgZXN0w6luIGVuIGZvcm1hdG8gQVNDSUkuIEVsIHJlc3VsdGFkbyBmaW5hbCBlcyB1biBkYXRhZnJhbWUgbGxhbWFkbyAic3RvcF93b3Jkc18xIiBxdWUgY29udGllbmUgbGEgbGlzdGEgZGUgc3RvcHdvcmRzIG5vcm1hbGl6YWRhcy4KCnN0b3Bfd29yZHNfMiA8LSByZWFkX2Nzdigiel9zdG9wd29yZHMudHh0IiwgY29sX25hbWVzID0gRkFMU0UpICU+JSAKICByZW5hbWUod29yZCA9IFgxKSAlPiUgCiAgIG11dGF0ZSh3b3JkPXN0cmluZ2k6OnN0cmlfdHJhbnNfZ2VuZXJhbCh3b3JkLCAiTGF0aW4tQVNDSUkiKSkKCiMgw41kZW0gcGVybyBlbCBhcmNoaXZvIENTViBsbGFtYWRvICJ6X3N0b3B3b3Jkcy50eHQiIHNlIGNhcmdhIGRlc2RlIGxhIGNhcnBldGEuCgpzdG9wX3dvcmRzX2Z1bGwgPC0gc3RvcF93b3Jkc18xICU+JSAKICBiaW5kX3Jvd3Moc3RvcF93b3Jkc18yKSAlPiUgCiAgZGlzdGluY3QoKQoKIyBFbiBlc3RlIHBhc28gY29tYmluYW1vcyBsb3MgZG9zIGRhdGFmcmFtZXMgZGUgc3RvcHdvcmRzLCBlbGltaW5hbW9zIGxhcyBmaWxhcyBkdXBsaWNhZGFzIHkgZ3VhcmRhbW9zIGVsIHJlc3VsdGFkbyBlbiB1biBudWV2byBkYXRhZnJhbWUgbGxhbWFkbyAic3RvcF93b3Jkc19mdWxsIi4gRXN0ZSBkYXRhZnJhbWUgY29udGllbmUgbGEgbGlzdGEgY29tcGxldGEgeSDDum5pY2EgZGUgc3RvcHdvcmRzIG5vcm1hbGl6YWRhcy4KCmNvcnB1c190aWR5IDwtIGNvcnB1c190aWR5ICU+JSAKICBhbnRpX2pvaW4oc3RvcF93b3Jkc19mdWxsKQoKIyBFbiBlc3RlIGPDs2RpZ28gZWxpbWluYW1vcyBsYXMgc3RvcHdvcmRzIGRlbCBjb3JwdXMgZGUgdGV4dG8gImNvcnB1c190aWR5IiB1dGlsaXphbmRvIGxhIGxpc3RhIGRlIHN0b3B3b3JkcyBjb250ZW5pZGEgZW4gZWwgZGF0YWZyYW1lICJzdG9wX3dvcmRzX2Z1bGwiLiBFc3RlIHBhc28gZW4gZWwgcHJvY2VzYW1pZW50byBkZSB0ZXh0byBlcyBuZWNlc2FyaW8gcGFyYSBlbGltaW5hciBwYWxhYnJhcyBxdWUgbm8gYXBvcnRhbiBzaWduaWZpY2FkbyBwYXJhIGVsIGFuw6FsaXNpcyBwb3N0ZXJpb3IuCmBgYAoKIyMjIENvcnJlY2Npw7NuICJhw7FvcyIKCmBgYHtyIGNvcnJlY2Npb259CmNvcnB1c190aWR5IDwtIGNvcnB1c190aWR5ICU+JSAKICBtdXRhdGUod29yZCA9IGNhc2Vfd2hlbigKICAgIHdvcmQgPT0gJ2FubycgfiAnYW5pbycsCiAgICB3b3JkID09ICdhbm9zJyB+ICdhbmlvJywKICAgIFRSVUUgfiB3b3JkCiAgKSkKYGBgCgojIyBFdGFwYSAyOiBDb25zaWduYXMKCsK/Q3XDoWxlcyBzb24gbGFzIHBhbGFicmFzIG3DoXMgdXRpbGl6YWRhcyBlbiBjYWRhIHVubyBkZSBsb3MgbWVkaW9zPyDCv1B1ZWRlbiB2ZXJzZSBkaWZlcmVuY2lhcz8gKFRlbmVyIGVuIGN1ZW50YSBsYXMgZGlmZXJlbnRlcyBtw6l0cmljYXMgdHJhYmFqYWRhcyBlbiBlbCBjdXJzbzogdGYsIHRmLWlkZiwgZXRjLikgR2VuZXJhciBsYXMgdmlzdWFsaXphY2lvbmVzIHF1ZSBjb25zaWRlcmUgbcOhcyBwZXJ0aW5lbnRlcyBwYXJhIHJlc3BvbmRlciBsYSBwcmVndW50YS4KCiMjIyBFeHBsb3JhY2nDs24gcGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzCgpDb21lbnphbW9zIGV4cGxvcmFuZG8gZWwgY29ycHVzIGVuIGZvcm1hdG8gdGlkeSBwYXJhIGlkZW50aWZpY2FyIHLDoXBpZGFtZW50ZSBhbGd1bm9zIGRlIGxvcyB0w6lybWlub3MgbcOhcyBmcmVjdWVudGVzIHBvciBtZWRpby4KCmBgYHtyIGV4cGxvcmVfZnJlcXN9CmNvcnB1c190aWR5ICU+JQogICAgICAgIGdyb3VwX2J5KHdvcmQsIG1lZGlvKSAlPiUKICAgICAgICBzdW1tYXJpc2Uobj1uKCkpICU+JQogICAgICAgIGFycmFuZ2UoZGVzYyhuKSkKCiMgTWVkaWFudGUgZXN0ZSBjw7NkaWdvIGFncnVwYW1vcyBsYXMgcGFsYWJyYXMgw7puaWNhcyBlbiBlbCBjb3JwdXMsIGNvbnRhbW9zIGVsIG7Dum1lcm8gZGUgb2N1cnJlbmNpYXMgZGUgY2FkYSBwYWxhYnJhIHkgbGFzIG9yZGVuYW1vcyBlbiBmdW5jacOzbiBkZSBzdSBmcmVjdWVuY2lhIGRlIGFwYXJpY2nDs24sIG1vc3RyYW5kbyBwcmltZXJvIGxhcyBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMuCgpjb3JwdXNfdGlkeSAlPiUKICBmaWx0ZXIobWVkaW8gPT0gJ2luZm9iYWUnKSAgJT4lIAogIGdyb3VwX2J5KHdvcmQpICU+JQogIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgYXJyYW5nZShkZXNjKG4pKQogICAgICAgIAojIE1pcmFkYSByw6FwaWRhIGEgbGFzIHBhbGFicmFzIG3DoXMgZnJlY3VlbnRlcyBkZSBhbGd1bm9zIG1lZGlvcwoKY29ycHVzX3RpZHkgJT4lCiAgICAgICAgZ3JvdXBfYnkobWVkaW8sIHdvcmQpICU+JQogICAgICAgIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgICAgICAgYXJyYW5nZShkZXNjKG4pKSAlPiUKICAgICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbWVkaW8sCiAgICAgICAgICAgICAgICAgICAgdmFsdWVzX2Zyb20gPSBuKQoKIyBFc3RlIGPDs2RpZ28gY2FsY3VsYSBlbCByZWN1ZW50byBkZSBvY3VycmVuY2lhcyBkZSBjYWRhIHBhbGFicmEgZW4gZWwgY29ycHVzIGRlIHRleHRvIGFncnVwYWRvIHNlZ8O6biBlbCBtZWRpbyBkZSBjb211bmljYWNpw7NuLCB5IGx1ZWdvIHJlb3JnYW5pemEgZXN0b3MgcmVzdWx0YWRvcyBlbiB1biBmb3JtYXRvIG3DoXMgYW5jaG8gZG9uZGUgY2FkYSBtZWRpbyB0aWVuZSBzdXMgcmVjdWVudG9zIGRlIG9jdXJyZW5jaWFzIGFzb2NpYWRvcyBwYXJhIGNhZGEgcGFsYWJyYS4KCmBgYAoKYGBge3Igd29yZF9jb3VudHN9CndvcmRfY291bnRzXzEwIDwtIGNvcnB1c190aWR5ICU+JQogICAgICAgIGdyb3VwX2J5KG1lZGlvLCB3b3JkKSAlPiUKICAgICAgICBzdW1tYXJpc2Uobj1uKCkpICU+JQogICAgICAgIHNsaWNlX21heChuLCBuID0gMTApICU+JSAKICAgICAgICB1bmdyb3VwKCkKCiMgRW4gZXN0ZSBwYXNvIGNhbGN1bGFtb3MgZWwgcmVjdWVudG8gZGUgb2N1cnJlbmNpYXMgZGUgY2FkYSBwYWxhYnJhIGVuIGVsIGNvcnB1cywgYWdydXBhZGFzIHBvciBsYSBjb2x1bW5hICJtZWRpbyIsIHkgYWxtYWNlbmEgZXN0b3MgcmVjdWVudG9zIGVuIHVuIG51ZXZvIGRhdGFmcmFtZSBsbGFtYWRvICJ3b3JkX2NvdW50cyIgcGFyYSBwYXNhcmxlIGEgbGEgZnVuY2nDs24gZGUgZ3JhZmljYWRvLgoKd29yZF9jb3VudHNfYWxsXzEwIDwtIGNvcnB1c190aWR5ICU+JQogICAgICAgIGdyb3VwX2J5KHdvcmQpICU+JQogICAgICAgIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgICAgICAgc2xpY2VfbWF4KG4sIG4gPSAxMCkgJT4lIAogICAgICAgIHVuZ3JvdXAoKSAlPiUgCiAgICAgICAgbXV0YXRlKG1lZGlvID0gJ2dlbmVyYWwnKQoKIyBBcXXDrSBjYWxjdWxhbW9zIGVsIHJlY3VlbnRvIGRlIG9jdXJyZW5jaWFzIGRlIGNhZGEgcGFsYWJyYSBlbiBlbCBjb3JwdXMsIHNpbiBhZ3J1cGFyIHBvciBtZWRpby4gTHVlZ28sIHNlbGVjY2lvbmFtb3MgbGFzIDEwIHBhbGFicmFzIG3DoXMgY29tdW5lcyBlbiB0b2RvIGVsIGNvcnB1cy4gQSBlc3RvcyByZXN1bHRhZG9zIGxlcyBhZ3JlZ2Ftb3MgbGEgZXRpcXVldGEgImdlbmVyYWwiIGVuIGxhIGNvbHVtbmEgIm1lZGlvIiBwYXJhIHBvZGVyIGNvbXBhcmFyIHZpc3VhbG1lbnRlIGxhIGRpc3RyaWJ1Y2nDs24gZ2VuZXJhbCBjb24gbGEgZGlzdHJpYnVjacOzbiBwb3IgbWVkaW8KCndvcmRfY291bnRzX3RvcF8xMCA8LSB3b3JkX2NvdW50c18xMCAlPiUgCiAgYmluZF9yb3dzKHdvcmRfY291bnRzX2FsbF8xMCkKCiMgVW5pZmljbyBlbiB1biBkYXRhc2V0CmBgYAoKQSBjb250aW51YWNpw7NuIGNyZWFtb3MgdW5hIGZ1bmNpw7NuIHBhcmEgYXV0b21hdGl6YXIgdG9kYXMgbGFzIHZpc3VhbGl6YWNpb25lcyByZWxhY2lvbmFkYXMgYSBsYXMgbcOpdHJpY2FzIGRlbCBjb3JwdXMuIAoKYGBge3IgY3JlYXJfdml6fQpjcmVhcl9ncmFmX3dvcmRzIDwtIGZ1bmN0aW9uKGRhdGEpIHsKICBtZWRpb3MgPC0gdW5pcXVlKGRhdGEkbWVkaW8pCiAgZ3JhZmljb3MgPC0gbGlzdCgpCiAgCiAgZm9yIChtZWRpb19hY3R1YWwgaW4gbWVkaW9zKSB7CiAgICBkYXRvc19tZWRpbyA8LSBkYXRhICU+JQogICAgICBmaWx0ZXIobWVkaW8gPT0gbWVkaW9fYWN0dWFsKSAlPiUKICAgICAgbXV0YXRlKHdvcmQgPSBmY3RfcmVvcmRlcih3b3JkLCBuKSkKICAgIAogICAgZ3JhZmljbyA8LSBnZ3Bsb3QoZGF0b3NfbWVkaW8sIGFlcyhuLCB3b3JkKSkgKwogICAgICAgICAgICAgICAgZ2VvbV9jb2woKSArCiAgICAgICAgICAgICAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbiksIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLCBmYW1pbHkgPSAiQ291cmllciIsIGNvbG9yID0gIndoaXRlIikgKwogICAgICAgICAgICAgICAgbGFicyh0aXRsZSA9IG1lZGlvX2FjdHVhbCwKICAgICAgICAgICAgICAgICAgICAgeCA9ICJGcmVjdWVuY2lhIiwKICAgICAgICAgICAgICAgICAgICAgeSA9ICJQYWxhYnJhIikgKwogICAgICAgICAgICAgICAgdGhlbWVfY2xhc3NpYygpKwogICAgICAgICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0gMC41KSwKICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJDb3VyaWVyIikpCiAgICAKICAgIGdyYWZpY29zW1ttZWRpb19hY3R1YWxdXSA8LSBncmFmaWNvCiAgfQogIAogIHdyYXBfcGxvdHMoZ3JhZmljb3MpCn0KCiMgRXN0YSBmdW5jacOzbiBjcmVhIHBhcmEgY2FkYSBtZWRpbyB1biBncsOhZmljbyBjb24gZWwgdG9wIDEwIGRlIHBhbGFicmFzIHNlZ8O6biBjYWRhIG3DqXRyaWNhIGFuYWxpemFkYQoKYGBgCgpFbCBlamUgeCBtdWVzdHJhIGxhcyBwYWxhYnJhcyB5IGVsIGVqZSB5IG11ZXN0cmEgbGEgZnJlY3VlbmNpYSBkZSBjYWRhIHBhbGFicmEuIEVsIGdyw6FmaWNvIGVzdMOhIHNlZ21lbnRhZG8gZW4gcGFuZWxlcyBwb3IgY2FkYSBtZWRpbywgeSBsYXMgZXNjYWxhcyBkZWwgZWplIHkgc2UgYWp1c3RhbiBwYXJhIGNhZGEgcGFuZWwgaW5kaXZpZHVhbC4KCmBgYHtyIHZpen0KY3JlYXJfZ3JhZl93b3Jkcyh3b3JkX2NvdW50c190b3BfMTApCgojIEVzdGUgY8OzZGlnbyBnZW5lcmEgdW5hIHZpc3VhbGl6YWNpw7NuIGRlIGxhcyBwYWxhYnJhcyBtw6FzIGNvbXVuZXMgZW4gZWwgY29ycHVzLCBkZXNnbG9zYWRhcyBwb3IgbWVkaW8geSBlbiBnZW5lcmFsLiBMYXMgcGFsYWJyYXMgbcOhcyBjb211bmVzIHNlIG11ZXN0cmFuIGVuIG9yZGVuIGRlc2NlbmRlbnRlIGRlIGZyZWN1ZW5jaWEgZW4gY2FkYSBjYXRlZ29yw61hLgpgYGAKCkFsZ3VuYXMgb2JzZXJ2YWNpb25lcyBwcmVsaW1pbmFyZXM6CgotICAgQ2xhcsOtbiBlIEluZm9iYWUgYXBvcnRhbiBjYWRhIHVubyBlbCAyMiUgZGUgbGFzIG5vdGljaWFzIGFsIGNvcnB1cy4gRWwgcGVzbyBkZSBsYXMgcGFsYWJyYXMgbcOhcyBpbXBvcnRhbnRlcyBwYXJhIGVzdG9zIG1lZGlvcyBlbiBlbCByYW5raW5nIGdlbmVyYWwgZXMgYWx0by4KCi0gICBBw7FvIHBhcmVjZSB1bmEgc3RvcHdvcmQgcG9ycXVlIGVzIHVuYSBwYWxhYnJhIHF1ZSBwcmVzZW50YSB1bmEgZnJlY3VlbmNpYSBhbHRhIGludHJhIGUgaW50ZXIgbWVkaW9zLgoKLSAgIENyw7NuaWNhLCBMTiBlIEluZm9iYWUgc29uIGxvcyDDum5pY29zIHF1ZSBubyBpbmNsdXllbiBub21icmVzIHByb3Bpb3MKZW50cmUgbGFzIHBhbGFicmFzIG3DoXMgZnJlY3VlbnRlcy4KCi0gICBFbiBMTiBoYXkgbXVjaGFzIHJlZmVyZW5jaWFzIGEgcmVkZXMgc29jaWFsZXMgeSBhY2Npb25lcyBlbiBlbGxhcyAoZ3VhcmRhciwgY29tcGFydGlyKS4gTGFzIGFjY2lvbmVzIHkgImZ1ZW50ZSIgcG9kcsOtYW4gc2VyIHN0b3B3b3Jkcy4gSW5jbHVzbyBsYXMgbWVuY2lvbmVzIGEgUlJTUyBwb2Ryw61hbiBzZXIgcHJvZHVjdG8gZGVsIHNjcmFwcGluZyB5IG5vIGRlbCBjb250ZW5pZG8gZGUgbGFzIG5vdGFzLgoKLSAgIE1hY3JpIGFwYXJlY2UgY29tbyBlbCBwb2zDrXRpY28gbcOhcyBub21icmFkby4gTMOzZ2ljYW1lbnRlLCBlcyB1biByZXN1bHRhZG8gZXNwZXJhYmxlIHNpIHNlIHRpZW5lIGVuIGN1ZW50YSBxdWUgbGFzIG5vdGFzIHJlbGV2YWRhcyBjb3JyZXNwb25kZW4gYWwgw7psdGltbyBhw7FvIGRlIHN1IGdvYmllcm5vLiAKCi0gIEluZm9iYWUgeSBzb2JyZSB0b2RvIENyw7NuaWNhIHBhcmVjZW4gY3VicmlyIHRlbWFzIG3DoXMgZ2VuZXJhbGVzIHF1ZSBsb3MgZGVtw6FzIG1lZGlvcywgYWwgaW5jbHVpciBwYWxhYnJhcyBjb21vICJwb2zDrWPDrWEiLCAiY2l1ZGFkIiwgIm11amVyIiwgImhvbWJyZSIsICJjYXNhIiwgInBlcnNvbmFzIiwgIm11bmRvIiwgInZpZGEiLiBJbmNsdXNvLCBsYSBmcmVjdWVuY2lhIGRlIGxhIHBhbGFicmEgInBvbGljw61hIiBlbiBDcsOzbmljYSAoIzIgbyAjMSBzaSBzZSBleGNsdXllIGVsIHTDqXJtaW5vICJhbmlvIikgcG9kcsOtYSBpbmRpY2FyIHF1ZSBsYSBtYXlvciBwYXJ0ZSBkZSBsYSBjb2JlcnR1cmEgZGUgZXN0ZSBtZWRpbyBzZSBvcmllbnRhIGEgbGEgdGVtw6F0aWNhIHBvbGljaWFsLiBEZSB0b2RhcyBmb3JtYXMsIGVuIGVsIHRvcCAxMCB0YW1iacOpbiBhcGFyZWNlbiBwYWxhYnJhcyBjb21vICJnb2JpZXJubyIgeSAicHJlc2lkZW50ZSIuCgotICAgTGEgcGFsYWJyYSAiZnJlbnRlIiBhcGFyZWNlIGNvbiBmcmVjdWVuY2lhIGVuIHZhcmlvcyBtZWRpb3MsIGxvIHF1ZSBub3MgaW5jbGluYSBhIGNvcnJvYm9yYXIgc2kgcmVmaWVyZSBhIGZyZW50ZXMgZWxlY3RvcmFsZXMgbyBzaW1wbGVtZW50ZSBhIHVuIGFkdmVyYmlvIGRlIGx1Z2FyIHksIGVuIGVzdGUgY2FzbywgZGViZXLDrWEgY29uc2lkZXJhcnNlIGNvbW8gc3RvcHdvcmQuCgotICAgQSBtb2RvIGRlIHPDrW50ZXNpcywgcG9kcsOtYSBhZHZlcnRpcnNlIHF1ZSBsYXMgcGFsYWJyYXMgY29uIG1heW9yIGZyZWN1ZW5jaWEgZGUgYXBhcmljacOzbiBlbiBsYSB0b3RhbGlkYWQgZGUgbG9zIG1lZGlvcyBzZSBjb3JyZXNwb25kZW4gY29uIGxhIHRlbcOhdGljYSBwb2zDrXRpY2EuIEVzIHByb2JhYmxlIHF1ZSBlc3RhIHZpbmN1bGFjacOzbiBzZWEgdW4gZGVzcHJlbmRpbWllbnRvIGRlbCBlc2NlbmFyaW8gZWxlY3RvcmFsIHF1ZSBzaWduw7MgZWwgcGVyw61vZG8gYmFqbyBhbsOhbGlzaXMuCgojIyMgVGVybSBGcmVxdWVuY3kgLSBURjoKCkEgbW9kbyBkZSBwcsOhY3RpY2EgZ2VuZXJhbW9zIGVsIGPDoWxjdWxvIGRlIHRlcm0gZnJlcXVlbmN5IG1hbnVhbG1lbnRlIHBhcmEgdmlzdWFsaXphciBsYSBkaXN0cmlidWNpw7NuIGRlIHTDqXJtaW5vcyBzZWfDum4gc3UgZnJlY3VlbmNpYSByZXNwZWN0byBhbCB0b3RhbCBkZSB0w6lybWlub3MsIGFwZXJ0dXJhZG8gcG9yIG1lZGlvLgoKYGBge3Igd29yZHNfdGlkeX0KIyBMYSAidGVybSBmcmVxdWVuY3kiIChmcmVjdWVuY2lhIGRlIHTDqXJtaW5vKSBlcyB1bmEgbcOpdHJpY2EgcGFyYSBldmFsdWFyIGxhIGltcG9ydGFuY2lhIGRlIHVuYSBwYWxhYnJhIGVuIHVuIGRvY3VtZW50byBvIGNvcnB1cyBkZSBkb2N1bWVudG9zLCBhIHBhcnRpciBkZSBsYSBtZWRpY2nDs24gZGUgbGEgZnJlY3VlbmNpYSBkZSBzdSBhcGFyaWNpw7NuIGVuIGNvbXBhcmFjacOzbiBjb24gZWwgbsO6bWVybyB0b3RhbCBkZSBwYWxhYnJhcy4KCndvcmRzX3RpZHkgPC0gY29ycHVzX3RpZHkgJT4lIAogIGdyb3VwX2J5KG1lZGlvLCB3b3JkKSAlPiUgIyBlbiBmdW5jacOzbiBkZSBsYSBjb25zaWduYSwgaGFjZW1vcyBlbCBjb250ZW8gZGUgdMOpcm1pbm9zIHBvciBtZWRpbwogIHN1bW1hcmlzZShuPW4oKSkgJT4lIAogIGFycmFuZ2UoZGVzYyhuKSkgCgp0b3RhbF93b3JkcyA8LSB3b3Jkc190aWR5ICU+JQogICAgICAgIGdyb3VwX2J5KG1lZGlvKSAlPiUKICAgICAgICBzdW1tYXJpemUodG90YWwgPSBzdW0obikpIAoKd29yZHNfdGlkeSA8LSB3b3Jkc190aWR5ICU+JSAKICBsZWZ0X2pvaW4odG90YWxfd29yZHMpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGFycmFuZ2UoZGVzYyhuKSkKCiMgRW4gZXN0ZSBjw7NkaWdvIGNvbnRhbW9zIGxhIGZyZWN1ZW5jaWEgZGUgY2FkYSBwYWxhYnJhIGVuIGVsIGNvcnB1cyBkZWwgbWVkaW8geSB0YW1iacOpbiBlbCB0b3RhbCBkZSBwYWxhYnJhcyBlbiBlbCBtZWRpby4gQ29uIGVzdG9zIGRhdG9zIHBvZGVtb3MgY2FsY3VsYXIgYSBjb250aW51YWNpw7NuIGxhIGltcG9ydGFuY2lhIGRlIGNhZGEgdMOpcm1pbm8gZW4gZWwgbWVkaW8uCmBgYAoKIyMgUFJVRUJJVEFTU1MKYGBge3J9CndvcmRzX3RpZHlfdGVzdCA8LSBjb3JwdXNfdGlkeSAlPiUgCiAgZ3JvdXBfYnkoaWQsIHdvcmQsIG1lZGlvKSAlPiUgCiAgc3VtbWFyaXNlKG49bigpKSAlPiUgCiAgYXJyYW5nZShkZXNjKG4pKSAKCnRvdGFsX3dvcmRzX3Rlc3QgPC0gd29yZHNfdGlkeV90ZXN0ICU+JQogICAgICAgIGdyb3VwX2J5KGlkKSAlPiUKICAgICAgICBzdW1tYXJpemUodG90YWwgPSBzdW0obikpIAoKd29yZHNfdGlkeV90ZXN0IDwtIHdvcmRzX3RpZHlfdGVzdCAlPiUgCiAgbGVmdF9qb2luKHRvdGFsX3dvcmRzX3Rlc3QpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGFycmFuZ2UoZGVzYyhuKSkKCnQgPC0gd29yZHNfdGlkeV90ZXN0ICU+JSBtdXRhdGUodGYgPSBuL3RvdGFsKSAlPiUKICAgICAgICAgIGdncGxvdChhZXModGYpKSArCiAgICAgICAgICAgICAgICBnZW9tX2hpc3RvZ3JhbShzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAgICAgICBjb29yZF9mbGlwKCkrCiAgICAgICAgICAgICAgICAjeGxpbShOQSwgMC4wMDAyKSArCiAgICAgICAgICAgICAgICBmYWNldF93cmFwKH5tZWRpbykgKwogICAgICAgICAgICAgICAgdGhlbWVfY2xhc3NpYygpKwogICAgICAgICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0gMC41KSwKICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJDb3VyaWVyIikpCiAgICAgICAgICAgICAgICAgIAoKYGBgCkVuIGVzdGEgdmlzdWFsaXphY2nDs24gcG9kZW1vcyBpbnRlcmFjdHVhciBwYXJhIGlkZW50aWZpY2FyIGxhIGNhbnRpZGFkIGRlIHTDqXJtaW5vcyBwb3IgdGY6CgpgYGB7ciB0Zl92aXpfbWFudWFsfQp0Zl92aXogPC0gd29yZHNfdGlkeSAlPiUgbXV0YXRlKHRmID0gbi90b3RhbCkgJT4lCiAgICAgICAgICBnZ3Bsb3QoYWVzKHRmKSkgKwogICAgICAgICAgICAgICAgZ2VvbV9oaXN0b2dyYW0oc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgICAgICAgY29vcmRfZmxpcCgpKwogICAgICAgICAgICAgICAgeGxpbShOQSwgMC4wMDAyKSArCiAgICAgICAgICAgICAgICBmYWNldF93cmFwKH5tZWRpbykgKwogICAgICAgICAgICAgICAgdGhlbWVfY2xhc3NpYygpKwogICAgICAgICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0gMC41KSwKICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJDb3VyaWVyIikpCiAgICAgICAgICAgICAgICAgIApnZ3Bsb3RseSh0Zl92aXopCgpgYGAKClZlbW9zIHF1ZSBwYXJhIGxvcyBtZWRpb3MgbcOhcyBtYXNpdm9zIHNlIGN1bXBsZSBsYSBMZXkgZGUgWmlwZjogcG9jYXMgcGFsYWJyYXMgb2N1cnJlbiBtdWNoYXMgdmVjZXMuIExvcyBjb3JwdXMgZGUgQ3LDs25pY2EsIE1pbnV0byBVbm8geSBUw6lsYW0gdGllbmVuIG1lbm9zIHTDqXJtaW5vcyBxdWUgbG9zIGRlbcOhcyB5IGxhIHRmIHRpZW5lIHVuIHJhbmdvIG1lbm9yICh1c28gZGUgcGFsYWJyYXMgbWVqb3IgZGlzdHJpYnVpZG8pLgoKIyMjIFRlcm0gRnJlcXVlbmN5IC0gSW52ZXJzZSBEb2N1bWVudCBGcmVxdWVuY3kgKFRGLUlERikKCkVuIGVzdGEgZXRhcGEgY29tcGxlbWVudGFtb3MgZWwgYW7DoWxpc2lzIGNvbiBsYXMgbcOpdHJpY2FzIGRlIGlkZiB5IHRmX2lkZiwgcGFyYSBvYnRlbmVyIHVuYSBtZWRpZGEgbcOhcyBjb21wbGV0YSBkZSBsYSBpbXBvcnRhbmNpYSBkZSBsb3MgdMOpcm1pbm9zIGVuIGVsIGNvcnB1cyBkZSBkb2N1bWVudG9zLgoKYGBge3IgdGZfaWRmfQp0Zl9pZGYgPC0gd29yZHNfdGlkeSAlPiUgCiAgYmluZF90Zl9pZGYod29yZCwgbWVkaW8sIG4pIAoKdGZfaWRmICU+JSBzZWxlY3QoLXRvdGFsKSAlPiUgCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpCgojIEVuIGVzdGUgY8OzZGlnbyBjYWxjdWxhbW9zIGxhcyBtw6l0cmljYXMgdGYsIGlkZiB5IHRmX2lkZiBwYXJhIHRvZG9zIGxvcyB0w6lybWlub3MgZGVsIGNvcnB1cwpgYGAKCkNyZWFtb3MgdHJlcyBkYXRhc2V0LCB1bm8gcG9yIG3DqXRyaWNhLCBjb24gZWwgdG9wIHRlbiBkZSB0w6lybWlub3MgcGFyYSBwYXNhcmxlIGEgbGEgZnVuY2nDs24gZGUgdmlzdWFsaXphY2nDs24uCgpgYGB7ciB0b3BfdGVuX21ldHJpY2FzfQp0Zl8xMCA8LSB0Zl9pZGYgJT4lCiAgICAgICAgZ3JvdXBfYnkobWVkaW8pICU+JQogICAgICAgIHNsaWNlX21heCh0ZiwgbiA9IDEwKSAlPiUgCiAgICAgICAgc2VsZWN0KG1lZGlvLCB3b3JkLCB0ZikgJT4lIAogICAgICAgIHJlbmFtZShuID0gdGYpICU+JQogICAgICAgIG11dGF0ZShuID0gbG9nKG4gKyAxKSAqIDEwMCkgJT4lIAogICAgICAgIG11dGF0ZShuID0gcm91bmQobiwgNCkpCgppZGZfMTAgPC0gdGZfaWRmICU+JQogICAgICAgICAgZ3JvdXBfYnkobWVkaW8pICU+JQogICAgICAgICAgYXJyYW5nZShkZXNjKGlkZikpICU+JQogICAgICAgICAgc2xpY2VfaGVhZChuID0gMTApICU+JQogICAgICAgICAgc2VsZWN0KG1lZGlvLCB3b3JkLCBpZGYpICU+JQogICAgICAgICAgcmVuYW1lKG4gPSBpZGYpICU+JQogICAgICAgICAgbXV0YXRlKG4gPSBsb2cobiArIDEpICogMTAwKSAlPiUgCiAgICAgICAgICBtdXRhdGUobiA9IHJvdW5kKG4sIDQpKQoKdGZfaWRmXzEwIDwtIHRmX2lkZiAlPiUKICAgICAgICBncm91cF9ieShtZWRpbykgJT4lCiAgICAgICAgYXJyYW5nZShkZXNjKGlkZikpICU+JQogICAgICAgIHNsaWNlX2hlYWQobiA9IDEwKSAlPiUgCiAgICAgICAgc2VsZWN0KG1lZGlvLCB3b3JkLCB0Zl9pZGYpICU+JSAKICAgICAgICByZW5hbWUobiA9IHRmX2lkZikgJT4lCiAgICAgICAgbXV0YXRlKG4gPSBsb2cobiArIDEpICogMTAwKSAlPiUgCiAgICAgICAgbXV0YXRlKG4gPSByb3VuZChuLCA0KSkKYGBgCgpQYXNhbW9zIGNhZGEgdW5vIGRlIGxvcyB0b3AgdGVuIGRlIHTDqXJtaW5vcyBwb3IgbGEgZnVuY2nDs24gZGUgZ3JhZmljYWRvIHBhcmEgdmlzdWFsaXphciBsb3MgdMOpcm1pbm9zIG3DoXMgaW1wb3J0YW50ZXMgc2Vnw7puIGNhZGEgbcOpdHJpY2EuCgpgYGB7ciB0Zl92aXp9CmNyZWFyX2dyYWZfd29yZHModGZfMTApCmBgYAoKYGBge3IgaWRmX3Zpen0KY3JlYXJfZ3JhZl93b3JkcyhpZGZfMTApCmBgYAoKYGBge3IgdGZfaWRmX3Zpen0KY3JlYXJfZ3JhZl93b3Jkcyh0Zl9pZGZfMTApCmBgYAoKQW7DoWxpc2lzIHByZWxpbWluYXIgZGUgVEYtSURGOgoKLSAgIEVuIHByaW1lciBsdWdhciBpZGVudGlmaWNhbW9zIGxhIG5lY2VzaWRhZCBkZSBhZ3JlZ2FyIHBhbGFicmFzIGFsCiAgICBsaXN0YWRvIGRlIHN0b3B3b3Jkcy4gRW50ZW5kZW1vcyBxdWUgbm8gYXBvcnRhbiBpbmZvcm1hY2nDs24gc29icmUgZWwKICAgIGNvbnRlbmlkbyB1IG9yaWVudGFjacOzbiBkZSBsYXMgbm90aWNpYXMgeSBhIHN1IHZleiBzb24gw7puaWNvcyBwYXJhCiAgICBhbGd1bm9zIG1lZGlvcyB5IGVzbyBnZW5lcmEgdW4gYWx0byB0Zl9pZGYuCgogICAgRWplbXBsb3M6IGpwZSwgYXAsIGVtaiwgY3JvbmljYS5jb20uYXIsIGZ2YXpxdWV6LCBjcm9uaWNhdmlyYWxlcywKICAgIGhkLCBwZW1leCwgZ3QsIGFmdiwganBnLCBtaW51dG91bm8uY29tLCBhbWJpdG8uY29tLCBpdmFub3ZpY2gsCiAgICBsb2FkaW5nLCBwYWdpbmFpLCBwcm90ZWN0ZWQsIGx4cywgZW1haWwsIHIuYyxjcCwgZmVsLGwubCxkLnMsIGVhLAogICAgZi5mLCBmLmQucywgZmgsIGEuZywgcGN0LCB0ZWxhbS5sYSwgbmFjaW9uYWwuZWwKCi0gICBQb2RlbW9zIGlkZW50aWZpY2FyICJmaXJtYXMiIGRlIHBlcmlvZGlzdGFzIChub21icmVzIGRlIHVzdWFyaW8gbwogICAgc2lnbGFzKSBxdWUgdGFtcG9jbyBzb24gaW5mb3JtYXRpdmFzIHNvYnJlIGVsIGNvbnRlbmlkbyBkZWwgY29ycHVzLgoKIyMgRXRhcGEgMS4yOiBBbXBsaWFjacOzbiBzdG9wd29yZHMgeSByZXByb2Nlc2FtaWVudG8gZGUgbGEgYmFzZQoKRWwgYW7DoWxpc2lzIHJlYWxpemFkbyBub3MgcGVybWl0acOzIGVuY29udHJhciBudW1lcm9zYXMgcGFsYWJyYXMgaW5jbHVpZGFzIGVuIGVsIGNvcnB1cyBxdWUgY3JlZW1vcyBzb24gcHJvZHVjdG8gZGVsIHNjcmFwcGluZy4gUHJldmlvIGEgbGEgbW9kZWxpemFjacOzbiBwYXJhIGlkZW50aWZpY2FyIHTDs3BpY29zIGNyZWVtb3MgcGVydGluZW50ZSBhbXBsaWFyIGVsIGxpc3RhZG8gZGUgc3RvcHdvcmRzIHkgcmVwZXRpciBlbCBwcmVwcm9jZXNhbWllbnRvIGRlIGxvcyBkYXRvcy4gCgpQYXJhIGFtcGxpYXIgZWwgbGlzdGFkbyBkZSBzdG9wd29yZHMgcGFydGltb3MgZGVsIGxpc3RhZG8gZGUgcGFsYWJyYXMgY29uIHN1cyBtw6l0cmljYXMgZGUgdGYgZSBpZGYuCgpgYGB7ciBzdG9wd29yZHNfdjJ9CiMgTWVkaWFudGUgZWwgc2lndWllbnRlIGPDs2RpZ28gZWxpbWluYW1vcyBwYWxhYnJhcyBxdWUgaW5jbHV5ZW4gcHVudG9zIHkgcGFyZWNlbiBwcm9kdWN0byBkZSBzY3JhcHBpbmcgd2ViLiAKbnVldmFzX3N0b3B3b3Jkc18xIDwtIHRmX2lkZiAlPiUgCiAgc2VsZWN0KHdvcmQpICU+JSAKICBmaWx0ZXIoZ3JlcGwoJ1xcLicsIHdvcmQpKSAlPiUgCiAgcHVsbCh3b3JkKQoKIyBFbiBlc3RlIHBhc28gZWxpbWluYW1vcyBwYWxhYnJhcyBkZSBkb3MgY2FyYWN0ZXJlcywgc2Fsdm8gY29udGFkYXMgZXhjZXBjaW9uZXMgcXVlIHRpZW5lbiBzZW50aWRvLgpudWV2YXNfc3RvcHdvcmRzXzIgPC0gdGZfaWRmICU+JSAKICBzZWxlY3Qod29yZCkgJT4lIAogIGZpbHRlcihzdHJfbGVuZ3RoKHdvcmQpID09IDIgJiAhKHdvcmQgJWluJSBjKCdwaicsICdmZScsICdkdCcsICd0dicsICdrbScsICdjdicsICdkcicsICdpdCcsICdkaicsICd1eCcpKSkgJT4lIAogIHB1bGwod29yZCkKCiMgRXN0ZSBjw7NkaWdvIGVsaW1pbmEgcGFsYWJyYXMgZGUgdHJlcyBjYXJhY3RlcmVzLiBQYXJlY2UgaGFiZXIgbcOhcyBzaWdsYXMgeSBwYWxhYnJhcyBjb3J0YXMgcXVlIHRpZW5lbiBzZW50aWRvIHBlcm8sIGRhZGEgbGEgZ3JhbiBjYW50aWRhZCBkZSBzdG9wd29yZHMgZGUgMyBjYXJhY3RlcmVzLCBoYXkgbW90aXZvcyBwYXJhIGluY2x1aXIgZXN0ZSBwYXNvLgpudWV2YXNfc3RvcHdvcmRzXzMgPC0gdGZfaWRmICU+JSAKICBzZWxlY3Qod29yZCkgJT4lIAogIGZpbHRlcihzdHJfbGVuZ3RoKHdvcmQpID09IDMgJiAhKHdvcmQgJWluJSBjKCdzYW4nLCAnbWlsJywgJ2xleScsICdhZnAnLCAnc3VyJywgJ2ZtaScsICd1c2QnLCAncmlvJywgJ2dvbCcsICdwYXonLCAnbWFyJywgJ29ybycsICdyZWQnLCAnbHV6JywgJ3ZveicgLCAncm9sJywgJ3NvbCcsICdnYXMnLCAncGllJywgJ3BhcicsICdwcm8nLCAndmlhJywgJ29udScsICd5cGYnLCAnaXZhJywgJ2FmYScsICdwYmknLCAnYmFyJywgJ2NmaycsICdlamUnLCAncmV5JywgJ2F0cCcsICdkb24nLCAnZmJpJywgJ2dheScsICdwc2cnLCAndWJhJywgJ3VjcicsICdjZW8nLCAnb25nJywgJ2ZlZCcsICdvam8nLCAnZGVhJywgJ3V2YScsICdjZ3QnLCAndWZpJywgJ2FwcCcsICdnaWwnLCAndmloJywgJ25iYScsICdiYmMnLCAnZXZvJywgJ2hpcCcsICdob3AnLCAnZm94JywgJ25iYycsICdyYXAnLCAnYWRuJywgJ2FsYScsICdldmEnLCAncGFuJywgJ3plbicsICdhZnYnLCAnY2NrJywgJ2VjbycsICdvY2EnLCAndGlvJywgJ2NubicsICdjaWEnLCAiZG5pIiwgInVpYSIsICJmZHQiLCAidWlmIiwgImhibyIsICJtbHMiLCAib2VhIiwgIm1lcCIsICJnbmMiLCAiYXVoIiwgImNoZSIsICJvaWwiLCAiZ2VuIiwgImFnbiIsCSJmcHYiLCAibGFtIiwJInBpYiIsICJjbmUiLCJkdW8iLCAidm94IiwgImRvdyIsICJ1Y2EiLCAiZG51IiwgInBleiIsICJwZmEiLCAicGR0IiwgImZkYSIsICJmY2kiLCAib21zIiwgInBzYSIsICJmZW8iLCAiY3RhIiwgImVnbyIsCQkJCQoiZmFhIiwJInV0ZSIsICJhdGUiLAkibGlvIiwgImZwdCIsICJzdXYiLCAiZ2JhIiwJIml6cSIsICJhcm8iLCAic21uIiwgInRudCIsCSJ1Y28iLCAiaXBjIiwgInNhYSIsICJ0bXoiLCAiY2NsIiwgImdlbCIsICJ2aXAiLCAiZXNpIiwJInJlcyIsICJrdW4iLCAidHNqIiwgImFmaSIsCSJwdHMiLCAiY25oIiwgImFqbyIsICJhY3YiLCAiYm13IiwgImJ1cyIsICJncHMiLCAiaWxlIiwJImlvcyIsICJ1bmMiLAkiem9vIiwgImp1cCIsICJ0b3MiLCAidW5sIiwJInVwbCIsICJ6ZW8iLCAiYXZlIiwgIm10ZSIsICJtcG4iLCAiYXBuIiwgIm1hbyIsICJwYmEiLCAic21zIiwgImNudiIsICJtZHoiLCAiZm9sIiwgImlzbyIpKSkgJT4lIAogIHB1bGwod29yZCkKCm51ZXZhc19zdG9wd29yZHMgPC0gZGF0YS5mcmFtZSh3b3JkID0gbnVldmFzX3N0b3B3b3Jkc18xKSAlPiUgCiAgYmluZF9yb3dzKGRhdGEuZnJhbWUod29yZCA9IG51ZXZhc19zdG9wd29yZHNfMikpICU+JSAKICBiaW5kX3Jvd3MoZGF0YS5mcmFtZSh3b3JkID0gbnVldmFzX3N0b3B3b3Jkc18zKSkKCnN0b3Bfd29yZHNfZnVsbF92MiA8LSBzdG9wX3dvcmRzX2Z1bGwgJT4lIAogIGJpbmRfcm93cyh0aWJibGUod29yZCA9IGMoJ2VtYmVkJywgJ2FuaW8nLCAnYW5vJywgJ2Fub3MnLCAnZ3VzdGEnLCAndHdpdHRlcicsICdmYWNlYm9vaycsICdjb21lbnRhcicsICdmdWVudGUnLCAnd2hhdHNhcHAnLCAnZ3VhcmRhcicsICdjb21wYXJ0aXInLCAnbWFpbCcsICdsb2FkaW5nJywgJ2VtYWlsJywgJ3BhZ2luYWknLCAnZWx0cmVjZScsICdpbmZvYmFlJywgJ2h0dHAnLCAnaHR0cHMnLCAnYXR0cmlidXRlJywgJ2ZpbmRfYWxsJywgJ25vbmV0eXBlJywgJ29iamVjdCcsICdyZWFkJyApKSkgJT4lIAogIGJpbmRfcm93cyhudWV2YXNfc3RvcHdvcmRzKQpgYGAKCkEgcGFydGlyIGRlIGxhIG51ZXZhIGxpc3RhIGRlIHN0b3B3b3JkcywgcmVjcmVhbW9zIGVsIGNvcnB1cyBlbiBmb3JtYXRvIHRpZHkgeSB0YW1iacOpbiBlbCBkYXRhZnJhbWUgZW4gZm9ybWF0byBtZWRpby90w6lybWluby9uIHBhcmEgY29udGludWFyIGNvbiBlbCBhbsOhbGlzaXMgZGUgbcOpdHJpY2FzLgoKYGBge3IgY29ycHVzX3RpZHlfdjJ9CmNvcnB1c190aWR5X3YyIDwtIGNvcnB1c190aWR5ICU+JSAKICBhbnRpX2pvaW4oc3RvcF93b3Jkc19mdWxsX3YyKQpgYGAKCmBgYHtyIHdvcmRzX3RpZHlfdjJ9IAp3b3Jkc190aWR5X3YyIDwtIGNvcnB1c190aWR5X3YyICU+JSAKICBncm91cF9ieShtZWRpbywgd29yZCkgJT4lCiAgc3VtbWFyaXNlKG49bigpKQoKdG90YWxfd29yZHNfdjIgPC0gd29yZHNfdGlkeV92MiAlPiUKICAgICAgICBncm91cF9ieShtZWRpbykgJT4lCiAgICAgICAgc3VtbWFyaXplKHRvdGFsID0gc3VtKG4pKSAKCndvcmRzX3RpZHlfdjIgPC0gd29yZHNfdGlkeV92MiAlPiUgCiAgbGVmdF9qb2luKHRvdGFsX3dvcmRzX3YyKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBhcnJhbmdlKGRlc2MobikpCmBgYAoKIyMjIFJldmlzacOzbiBkZSBtw6l0cmljYXMKCkNyZWFtb3MgZWwgZ3LDoWZpY28gY29uIGxhIGRpc3RyaWJ1Y2nDs24gZGUgdMOpcm1pbm9zIHNlZ8O6biBzdSB0ZiBwYXJhIGlkZW50aWZpY2FyIHZhcmlhY2lvbmVzIHJlc3BlY3RvIGEgbGEgdjEuCgpgYGB7ciB0Zl92aXpfbWFudWFsX3YyfQp0Zl92aXpfdjIgPC0gd29yZHNfdGlkeV92MiAlPiUgbXV0YXRlKHRmID0gbi90b3RhbCkgJT4lCiAgICAgICAgICBnZ3Bsb3QoYWVzKHRmKSkgKwogICAgICAgICAgICAgICAgZ2VvbV9oaXN0b2dyYW0oc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgICAgICAgY29vcmRfZmxpcCgpKwogICAgICAgICAgICAgICAgeGxpbShOQSwgMC4wMDAyKSArCiAgICAgICAgICAgICAgICBmYWNldF93cmFwKH5tZWRpbykgKwogICAgICAgICAgICAgICAgdGhlbWVfY2xhc3NpYygpKwogICAgICAgICAgICAgICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0gMC41KSwKICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJDb3VyaWVyIikpCiAgICAgICAgICAgICAgICAgIApnZ3Bsb3RseSh0Zl92aXpfdjIpCmBgYAoKCmBgYHtyIHRmX2lkZl92Mn0KdGZfaWRmX3YyIDwtIHdvcmRzX3RpZHlfdjIgJT4lIAogIGJpbmRfdGZfaWRmKHdvcmQsIG1lZGlvLCBuKSAKYGBgCgpDcmVhbW9zIGxvcyBkYXRhc2V0IG5lY2VzYXJpb3MgcGFyYSB2aXN1YWxpemFyIGxvcyBwcmluY2lwYWxlcyB0w6lybWlub3MgcG9yIG3DqXRyaWNhIHkgcG9yIG1lZGlvLCBidXNjYW5kbyBkaWZlcmVuY2lhcyByZXNwZWN0byBhIGxhIHYxLgoKYGBge3IgdG9wX3Rlbl9tZXRyaWNhc192Mn0KdGZfMTBfdjIgPC0gdGZfaWRmX3YyICU+JQogICAgICAgIGdyb3VwX2J5KG1lZGlvKSAlPiUKICAgICAgICBhcnJhbmdlKGRlc2ModGYpKSAlPiUKICAgICAgICBzbGljZV9oZWFkKG4gPSAxMCkgJT4lCiAgICAgICAgc2VsZWN0KG1lZGlvLCB3b3JkLCB0ZikgJT4lIAogICAgICAgIHJlbmFtZShuID0gdGYpICU+JQogICAgICAgIG11dGF0ZShuID0gbG9nKG4gKyAxKSAqIDEwMCkgJT4lIAogICAgICAgIG11dGF0ZShuID0gcm91bmQobiwgNCkpCgppZGZfMTBfdjIgPC0gdGZfaWRmX3YyICU+JQogICAgICAgICAgZ3JvdXBfYnkobWVkaW8pICU+JQogICAgICAgICAgYXJyYW5nZShkZXNjKGlkZikpICU+JQogICAgICAgICAgc2xpY2VfaGVhZChuID0gMTApICU+JQogICAgICAgICAgc2VsZWN0KG1lZGlvLCB3b3JkLCBpZGYpICU+JQogICAgICAgICAgcmVuYW1lKG4gPSBpZGYpICU+JQogICAgICAgICAgbXV0YXRlKG4gPSBsb2cobiArIDEpICogMTAwKSAlPiUgCiAgICAgICAgICBtdXRhdGUobiA9IHJvdW5kKG4sIDQpKQoKdGZfaWRmXzEwX3YyIDwtIHRmX2lkZl92MiAlPiUKICAgICAgICBncm91cF9ieShtZWRpbykgJT4lCiAgICAgICAgYXJyYW5nZShkZXNjKGlkZikpICU+JQogICAgICAgIHNsaWNlX2hlYWQobiA9IDEwKSAlPiUgCiAgICAgICAgc2VsZWN0KG1lZGlvLCB3b3JkLCB0Zl9pZGYpICU+JSAKICAgICAgICByZW5hbWUobiA9IHRmX2lkZikgJT4lCiAgICAgICAgbXV0YXRlKG4gPSBsb2cobiArIDEpICogMTAwKSAlPiUgCiAgICAgICAgbXV0YXRlKG4gPSByb3VuZChuLCA0KSkKYGBgCgpgYGB7ciB0Zl92aXpfdjJ9CmNyZWFyX2dyYWZfd29yZHModGZfMTBfdjIpIAoKY3JlYXJfZ3JhZl93b3Jkcyh0Zl8xMCkKYGBgCgpgYGB7ciBpZGZfdml6X3YyfQpjcmVhcl9ncmFmX3dvcmRzKGlkZl8xMF92MikKYGBgCgpgYGB7ciB0Zl9pZGZfdml6X3YyfQpjcmVhcl9ncmFmX3dvcmRzKHRmX2lkZl8xMF92MikKYGBgCgpBbsOhbGlzaXMgZmluYWwgZGUgVEYtSURGOgoKLSAgIEVuIHRvZG9zIGxvcyBtZWRpb3MsIGV4Y2VwdG8gZW4gTGEgTmFjacOzbiBxdWUgbm8gbGEgaW5jbHXDrWEsIGxvZ3LDsyBlbGltaW5hcnNlIGxhIHBhbGFicmEgImFuaW8iIHF1ZSBmaWd1cmFiYSBlbiBlbCBwcmltZXIgbHVnYXIgZW4gdMOpcm1pbm9zIGRlIGZyZWN1ZW5jaWEuIEFkZW3DoXMsIGVuIGxvcyBjYXNvcyBkZSBDcsOzbmljYSB5IE1pbnV0byAxLCBzZSBlbGltaW7DsyBvdHJhIHBhbGFicmEgYWRpY2lvbmFsIGluY29ycG9yYW5kbywgZGUgZXNhIG1hbmVyYSwgZG9zIG51ZXZhcyBhbCB0b3AgZGllei4gCgotIEVsIGNhc28gZGUgTGEgTmFjacOzbiB5YSBzZSBtdWVzdHJhIG5vcm1hbGl6YWRvLCBzaW4gbGFzIHN0b3B3b3JkcyBxdWUgYXBhcmVjw61hbiBwcm9kdWN0byBkZWwgc2NyYXBpbmcuCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMgTW9kZWxhZG8gZGUgdMOzcGljb3M6IExEQQoKwr9DdcOhbGVzIHNvbiBsb3MgdMOzcGljb3MgcHJpbmNpcGFsZXMgZW4gZWwgY29ycHVzPyDCv1B1ZWRlbiBldmlkZW5jaWFyIGRpZmVyZW5jaWFzIGVuIGNhZGEgdW5vIGRlIGxvcyBtZWRpb3M/IEV4cGxpY2FyIHF1w6kgbcOpdG9kbyBzZSB1dGlsaXrDsyBwYXJhIHJlc3BvbmRlciBsYSBwcmVndW50YSwgY3XDoWxlcyBzb24gbG9zIHN1cHVlc3RvcyBkZWwgbWlzbW8uIEdlbmVyYXIgbGFzIHZpc3VhbGl6YWNpb25lcyBtw6FzIGFkZWN1YWRhcyBwYXJhIHJlc3BvbmRlciBhIGxhcyBwcmVndW50YXMuCgpQYXJhIGltcGxlbWVudGFyIHVuIG1vZGVsYWRvIGRlIHRlbWFzIGNvbiBMREEgbmVjZXNpdGFtb3MgY29uc3RydWlyIHVuYSBtYXRyaXogRFRNLgoKIyBBIFBBUlRJUiBERSBBQ8OBCmRlam8gbGFzIGRvcyBwcnVlYmFzIHBhcmEgcXVlIHB1ZWRhcyBjb21wYXJhci4gU2kgdXNhbW9zIGxhIG1hdHJpeiB0w6lybWluby1mcmVjdWVuY2lhIHBvciBNRURJTywgbGEgZGV0ZWNjacOzbiBkZSB0w7NwaWNvcyBkYSBjdWFscXVpZXIgY29zYS4gU2kgdXNhbW9zIGxhIG1hdHJpeiB0w6lybWluby1mcmVjdWVuY2lhIHBvciBOT1RJQ0lBIGVsIHJlc3VsdGFkbyB0aWVuZSBtw6FzIHNlbnRpZG8uIE5vIGVzdG95IHNlZ3VyYSBjb21vIHNlIGV4cGxpY2EsIHF1aXrDoXMgcG9kZW1vcyBlbmNvbnRyYXIgYWxndW5hIGp1c3RpZmljYWNpw7NuLiAKCi0tPiBOb3MgcXVlZGFtb3MgY29uIGxhIHZlcnNpw7NuIDIgKHBvciBpZCkKCmBgYHtyIGR0bV8xfQpkaXNjX2R0bV8xIDwtIHdvcmRzX3RpZHlfdjIgJT4lCiAgICAgICAgICAgICAgICBjYXN0X2R0bShtZWRpbywgd29yZCwgbikKCmBgYAoKCmBgYHtyIGR0bV8yfQpwYXJhX2Rpc2NfZHRtIDwtIGNvcnB1c190aWR5X3YyICU+JSAKICBncm91cF9ieShpZCwgd29yZCkgJT4lCiAgc3VtbWFyaXNlKG49bigpKQojIGNyZW8gdW4gY29udGVvIGRlIHBhbGFicmFzIHBvciBub3RpY2lhLCBubyBwb3IgbWVkaW8gCgpkaXNjX2R0bV8yIDwtIHBhcmFfZGlzY19kdG0gJT4lCiAgICAgICAgICAgICAgICBjYXN0X2R0bShpZCwgd29yZCwgbikKCmBgYAoKYGBge3IgTERBX29yaWdpbmFsfQpsZGFfb3JpZ181IDwtIExEQShkaXNjX2R0bV8xLCBrID0gNSwgY29udHJvbCA9IGxpc3Qoc2VlZCA9IDEyMzQpKSAgIyBrIGVzIGVsIG7Dum1lcm8gZGUgdMOzcGljb3MgYSBpZGVudGlmaWNhcgoKYXBfdG9waWNzIDwtIHRpZHkobGRhX29yaWdfNSwgbWF0cml4ID0gImJldGEiKSAKIyBsYSBmdW5jacOzbiB0aWR5IGNvbnZpZXJ0ZSBlbCBtb2RlbG8gYSB1biB0w7NwaWNvLXTDqXJtaW5vIHBvciBmaWxhCgphcF90b3BpY3MgJT4lCiAgbXV0YXRlKGJldGEgPSByb3VuZCgxMDAqYmV0YSw2KSkKCmFwX3RvcF90ZXJtcyA8LSBhcF90b3BpY3MgJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIHNsaWNlX21heChiZXRhLCBuID0gMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZSh0b3BpYywgLWJldGEpCgphcF90b3BfdGVybXMgJT4lCiAgbXV0YXRlKHRlcm0gPSByZW9yZGVyX3dpdGhpbih0ZXJtLCBiZXRhLCB0b3BpYykpICU+JQogIGdncGxvdChhZXMoYmV0YSwgdGVybSwgZmlsbCA9IGZhY3Rvcih0b3BpYykpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofiB0b3BpYywgc2NhbGVzPSdmcmVlX3knKSArCiAgc2NhbGVfeV9yZW9yZGVyZWQoKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKYGBge3J9CmxkYV92Ml81IDwtIExEQShkaXNjX2R0bV8yLCBrID0gNSwgY29udHJvbCA9IGxpc3Qoc2VlZCA9IDU1NSkpICAjIGsgZXMgZWwgbsO6bWVybyBkZSB0w7NwaWNvcyBhIGlkZW50aWZpY2FyCgphcF90b3BpY3NfNSA8LSB0aWR5KGxkYV92Ml81LCBtYXRyaXggPSAiYmV0YSIpIAojIGxhIGZ1bmNpw7NuIHRpZHkgY29udmllcnRlIGVsIG1vZGVsbyBhIHVuIHTDs3BpY28tdMOpcm1pbm8gcG9yIGZpbGEKCmFwX3RvcGljc181ICU+JQogIG11dGF0ZShiZXRhID0gcm91bmQoMTAwKmJldGEsNikpCgphcF90b3BfdGVybXNfNSA8LSBhcF90b3BpY3NfNSAlPiUKICBncm91cF9ieSh0b3BpYykgJT4lCiAgc2xpY2VfbWF4KGJldGEsIG4gPSAxNSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKHRvcGljLCAtYmV0YSkKCmFwX3RvcF90ZXJtc181ICU+JQogIG11dGF0ZSh0ZXJtID0gcmVvcmRlcl93aXRoaW4odGVybSwgYmV0YSwgdG9waWMpKSAlPiUKICBnZ3Bsb3QoYWVzKGJldGEsIHRlcm0sIGZpbGwgPSBmYWN0b3IodG9waWMpKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH4gdG9waWMsIHNjYWxlcz0nZnJlZV95JykgKwogIHNjYWxlX3lfcmVvcmRlcmVkKCkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKUGFyZWNpZXJhIHF1ZSBsb3MgY2luY28gdMOzcGljb3MgYXJyb2phZG9zIGNvbW8gcmVzdWx0YWRvIHRpZW5lbiB1biBzZW50aWRvIGRlZmluaWRvOgoKMS0gUG9sw610aWNhIE5hY2lvbmFsLyBFbGVjY2lvbmVzCjItIERlcG9ydGVzCjMtIFNvY2llZGFkCjQtIFBvbMOtdGljYSBJbnRlcm5hY2luYWwKNS0gRWNvbm9tw61hCgpBIGNvbnRpbnVhY2nDs24sIGhhY2Vtb3MgdW5hIG51ZXZhIHBydWViYSBwYXJhIGV4cGxvcmFyIHNpIGVzIHBvc2libGUgaWRlbnRpZmljYXIgdW5hIG1heW9yIGNhbnRpZGFkIGRlIHTDs3BpY29zIChrPTEwKToKCmBgYHtyfQpsZGFfdjJfMTAgPC0gTERBKGRpc2NfZHRtXzIsIGsgPSAxMCwgY29udHJvbCA9IGxpc3Qoc2VlZCA9IDEwMTApKSAgIyBrIGVzIGVsIG7Dum1lcm8gZGUgdMOzcGljb3MgYSBpZGVudGlmaWNhcgoKYXBfdG9waWNzXzEwIDwtIHRpZHkobGRhX3YyXzEwLCBtYXRyaXggPSAiYmV0YSIpIAojIGxhIGZ1bmNpw7NuIHRpZHkgY29udmllcnRlIGVsIG1vZGVsbyBhIHVuIHTDs3BpY28tdMOpcm1pbm8gcG9yIGZpbGEKCmFwX3RvcGljc18xMCAlPiUKICBtdXRhdGUoYmV0YSA9IHJvdW5kKDEwMCpiZXRhLDYpKQoKYXBfdG9wX3Rlcm1zXzEwIDwtIGFwX3RvcGljc18xMCAlPiUKICBncm91cF9ieSh0b3BpYykgJT4lCiAgc2xpY2VfbWF4KGJldGEsIG4gPSAxNSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKHRvcGljLCAtYmV0YSkKCmFwX3RvcF90ZXJtc18xMCAlPiUKICBtdXRhdGUodGVybSA9IHJlb3JkZXJfd2l0aGluKHRlcm0sIGJldGEsIHRvcGljKSkgJT4lCiAgZ2dwbG90KGFlcyhiZXRhLCB0ZXJtLCBmaWxsID0gZmFjdG9yKHRvcGljKSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcCh+IHRvcGljLCBzY2FsZXM9J2ZyZWVfeScpICsKICBzY2FsZV95X3Jlb3JkZXJlZCgpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCkNvbiBlc3RhIHBydWViYSwgcXVlIGR1cGxpY8OzIGxhIGNhbnRpZGFkIGRlIHTDs3BpY29zLCBjb21lbnphbW9zIGEgcmVnaXN0cmFyIGNvbmp1bnRvcyBxdWUgbm8gdGllbmVuIHVuIHNlbnRpZG8gdGFuIGRlZmluaWRvLCBjYXRhbG9nYWRvcyBwcm92aXNvcmlhbWVudGUgY29tbyB0w7NwaWNvcyBkZSAiU29jaWVkYWQiICgxLCAzIHkgNikuIEEgc3UgdmV6LCBlbiBkb3MgY2Fzb3MgZXMgcG9zaWJsZSByZWNvbm9jZXIgdW5hIHN1cGVycG9zaWNpw7NuIHRlbcOhdGljYSAoNCB5IDgpLgoKMS0gU29jaWVkYWQKMi0gUG9sw610aWNhIE5hY2lvbmFsLyBFbGVjY2lvbmVzCjMtIFNvY2llZGFkICjCv1ZpZGEgRmFtaWxpYXI/KQo0LSBQb2zDrXRpY2EgSW50ZXJuYWNpb25hbAo1LSBQb2xpY2lhbGVzCjYtIFNvY2llZGFkCjctIERlcG9ydGUKOC0gUG9sw610aWNhIEludGVybmFjaW9uYWwKOS0gRWNvbm9tw61hCjEwLSBDdWx0dXJhLyBDaW5lIHkgU2VyaWVzCgpFbiBlc2EgZGlyZWNjacOzbiwgYXZhbnphbW9zIGVuIHVuYSBudWV2YSBwcnVlYmEgcXVlIHByb3BvbmUgcmVkdWNpciBsZXZlbWVudGUgbGEgY2FudGlkYWQgZGUgdMOzcGljb3MgKGs9OCk6CgpgYGB7cn0KbGRhX3YyXzggPC0gTERBKGRpc2NfZHRtXzIsIGsgPSA4LCBjb250cm9sID0gbGlzdChzZWVkID0gODg4KSkgICMgayBlcyBlbCBuw7ptZXJvIGRlIHTDs3BpY29zIGEgaWRlbnRpZmljYXIKCmFwX3RvcGljc184IDwtIHRpZHkobGRhX3YyXzgsIG1hdHJpeCA9ICJiZXRhIikgCiMgbGEgZnVuY2nDs24gdGlkeSBjb252aWVydGUgZWwgbW9kZWxvIGEgdW4gdMOzcGljby10w6lybWlubyBwb3IgZmlsYQoKYXBfdG9waWNzXzggJT4lCiAgbXV0YXRlKGJldGEgPSByb3VuZCgxMDAqYmV0YSw2KSkKCmFwX3RvcF90ZXJtc184IDwtIGFwX3RvcGljc184ICU+JQogIGdyb3VwX2J5KHRvcGljKSAlPiUKICBzbGljZV9tYXgoYmV0YSwgbiA9IDE1KSAlPiUgCiAgdW5ncm91cCgpICU+JQogIGFycmFuZ2UodG9waWMsIC1iZXRhKQoKYXBfdG9wX3Rlcm1zXzggJT4lCiAgbXV0YXRlKHRlcm0gPSByZW9yZGVyX3dpdGhpbih0ZXJtLCBiZXRhLCB0b3BpYykpICU+JQogIGdncGxvdChhZXMoYmV0YSwgdGVybSwgZmlsbCA9IGZhY3Rvcih0b3BpYykpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofiB0b3BpYywgc2NhbGVzPSdmcmVlX3knKSArCiAgc2NhbGVfeV9yZW9yZGVyZWQoKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYApFbiBjb21wYXJhY2nDs24gY29uIGxhIHBydWViYSBhbnRlcmlvciwgdGFtYmnDqW4gZGV0ZWN0YW1vcyB0cmVzIHTDs3BpY29zIGNvbiB1biBzZW50aWRvIGRpZnVzbyAoMSwgMyB5IDcpLCBtaWVudHJhcyBxdWUgZGVzYXBhcmVjaWVyb24gdGFudG8gbGEgcmVwZXRpY2nDs24gZGVsIHTDs3BpY28gIlBvbMOtdGljYSBJbnRlcm5hY2lvbmFsIiBjb21vIGVsIHTDs3BpY28gIkN1bHR1cmEvIENpbmUgeSBTZXJpZXMiLgoKMS0gU29jaWVkYWQKMi0gRWNvbm9tw61hCjMtIFNvY2llZGFkCjQtIFBvbGljaWFsZXMKNS0gUG9sw610aWNhIEludGVybmFjaW9uYWwKNi0gRGVwb3J0ZXMKNy0gU29jaWVkYWQgKMK/WSBDdWx0dXJhPykKOC0gUG9sw610aWNhIE5hY2lvbmFsLyBFbGVjY2lvbmVzCgpUZW5pZW5kbyBlbiBjdWVudGEgbGFzIHRyZXMgcHJ1ZWJhcyByZWFsaXphZGFzIGNvbiBlbCBtb2RlbG8gTERBLCBlbnRlbmRlbW9zIHF1ZSBlbCBtZWpvciBtb2RlbG8gZXMgZWwgcXVlIHBsYW50ZWEgdW4gSz01LiBFUyBERUNJUiwgVE9ETyBMTyBRVUUgU0lHVUUgRVNUw4EgUExBTlRFQURPIERFU0RFIEVMIE1PREVMTyBERSBLPTUuCgojIERVREE6IGVuIGVsIGVqZW1wbG8gcXVlIGFwYXJlY2UgZW4gbGEgbm90ZWJvb2ssIGhheSBkb3MgdGVtYXMgcXVlIG5vIHRpZW5lbiB1biBzZW50aWRvIGRlZmluaWRvIHkgcG9yIGVzbyAiRXN0byBwYXJlY2UgdW4gcHJpbWVyIGluZGljYWRvciBkZSBxdWUgZGViZXLDrWFtb3MgY29uc2lkZXJhciBsYSBwb3NpYmlsaWRhZCBkZSB1dGlsaXphciB1biBuw7ptZXJvIGRlIHTDs3BpY29zIG3DoXMgZWxldmFkbyIuIEVuIG51ZXN0cm8gY2FzbyBsbyBlc3RveSBwZW5zYW5kbyBhbCByZXbDqXMgKG1lbm9zIGssIG1heW9yIGRlZmluaWNpw7NuIGRlIHTDs3BpY29zKS4KCkxhIHZpc3VhbGl6YWNpw7NuIGNvcnJlc3BvbmRpZW50ZSBhIGs9NSBtdWVzdHJhIHF1ZSBhbGd1bmFzIHBhbGFicmFzIGNvbW8gImFyZ2VudGluYSIsICJwZXJzb25hcyIgc29uIGNvbXVuZXMgYSBtw6FzIGRlIHVuIHRlbWEuIEVzIGRlY2lyLCBxdWUgbG9zIHTDs3BpY29zIGlkZW50aWZpY2Fkb3MgdGllbmVuIGNpZXJ0YSBzdXBlcnBvc2ljacOzbiBlbiB0w6lybWlub3MgZGUgcGFsYWJyYXMuIENvbW8gYWx0ZXJuYXRpdmEsIHBvZHLDrWFtb3MgY29uc2lkZXJhciBsb3MgdMOpcm1pbm9zIHF1ZSB0dXZpZXJhbiBsYSBtYXlvciBkaWZlcmVuY2lhIGVuIM6yIGVudHJlIGVsIHRlbWEgMSB5IGVsIHRlbWEgNSAocXVlIHNvbiBkb3MgZGUgbG9zIHF1ZSBtZWpvciBwb2RlbW9zIGludGVycHJldGFyKS4gTk8gTUUgUVVFREEgREVMIFRPRE8gQ0xBUk8sIFBFUk8gTUUgUEFSRUNFIFFVRSBFU1RFIFBBU08gUEVSTUlURSBDT1JST0JPUkFSIFFVRSBET1MgVMOTUElDT1MgU0VBTiBMTyBTVUZJQ0lFTlRFTUVOVEUgRElGRVJFTlRFUy4gCgpgYGB7cn0KYmV0YV93aWRlIDwtIGFwX3RvcGljc181ICU+JQogIG11dGF0ZSh0b3BpYyA9IHBhc3RlMCgidG9waWMiLCB0b3BpYykpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB0b3BpYywgdmFsdWVzX2Zyb20gPSBiZXRhKSAlPiUgCiAgZmlsdGVyKHRvcGljMSA+IC4wMDIgfCB0b3BpYzUgPiAuMDAyKSAlPiUKICBtdXRhdGUobG9nX3JhdGlvMV81ID0gbG9nMih0b3BpYzUgLyB0b3BpYzEpKQoKYmV0YV93aWRlCmBgYApgYGB7cn0KYmV0YV93aWRlICU+JQogIGdncGxvdChhZXMoeD1yZW9yZGVyKHRlcm0sbG9nX3JhdGlvMV81KSAsIHk9bG9nX3JhdGlvMV81KSkgKwogICAgZ2VvbV9jb2woKSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgbGFicyh4PSdUw6lybWlubycsCiAgICAgICAgIHk9J0xvZzIgcmF0aW8gdG9waWM1L3RvcGljMScpICsKICAgIHRoZW1lX21pbmltYWwoKQpgYGAKClBhbGFicmFzIGNvbW8g4oCcdmlkYWwiLCAianVzdGljaWEiIG8gImtpcmNobmVyIiBjYXJhY3Rlcml6YW4gYWwgdMOzcGljbyAxLCBtaWVudHJhcyBxdWUg4oCccHJvZHVjdG9z4oCdLCDigJxpbmZsYWNpw7Nu4oCdIG8gIm1lcmNhZG8iIHJlcHJlc2VudGFuIGFsIHTDs3BpY28gNS4gRXN0byBjb250cmlidXllIGEgY29uZmlybWFyIHF1ZSBzZSB0cmF0YSBkZSBkb3MgdMOzcGljb3MgZGlmZXJlbmNpYWRvcy4KCgpDb21wb3NpY2nDs24gZGUgdMOzcGljb3MgcG9yIGRvY3VtZW50bzoKU2kgYW50ZXMgZXN0aW1hbW9zIGNhZGEgdGVtYSBjb21vIHVuYSBtZXpjbGEgZGUgcGFsYWJyYXMsIGFob3JhIHVzYW1vcyBMREEgdGFtYmnDqW4gcGFyYSBtb2RlbGFyIGNhZGEgZG9jdW1lbnRvIGNvbW8gdW5hIG1lemNsYSBkZSB0ZW1hcy4gCgpgYGB7cn0KZG9jXzJfdG9waWNzIDwtIHRpZHkobGRhX3YyXzUsIG1hdHJpeCA9ICJnYW1tYSIpCmRvY18yX3RvcGljcyAlPiUKICBtdXRhdGUoZ2FtbWEgPSByb3VuZChnYW1tYSwgNSksCiAgICAgICAgIGRvY3VtZW50ID0gYXMuaW50ZWdlcihkb2N1bWVudCkpICU+JQogIGFycmFuZ2UoZG9jdW1lbnQsIGRlc2MoZ2FtbWEpKQpgYGAKQ2FkYSB1bm8gZGUgZXN0b3MgdmFsb3JlcyBlcyB1bmEgcHJvcG9yY2nDs24gZXN0aW1hZGEgZGUgcGFsYWJyYXMgZGUgZXNlIGRvY3VtZW50byBxdWUgc2UgZ2VuZXJhbiBhIHBhcnRpciBkZSBlc2UgdGVtYS4KCmBgYHtyfQpkb2NfMl90b3BpY3MgJT4lCiAgZmlsdGVyKHRvcGljID09IDEgfHRvcGljID09IDIgfHRvcGljID09IDMgfHRvcGljID09IDQgfCB0b3BpYyA9PSA1KSAlPiUgI2NyZW8gcXVlIGVzdGUgcGFzbyBubyB2YSBwb3JxdWUgZW4gZGVmaXRpdmEgZXN0b3kgZmlsdHJhbmRvIHRvZG9zIGxvcyB0w7NwaWNvcwogIG11dGF0ZShnYW1tYSA9IHJvdW5kKGdhbW1hLCA1KSkKYGBgCkVuIHBhcnRpY3VsYXIsIHNpIG9ic2VydmFtb3MgZWwgZG9jdW1lbnRvIDk0LCBsYSBwcm9iYWJpbGlkYWQgZ2FtbWEgYXNvY2lhZGEgYWwgdGVtYSAxIGVzIDAuOTk4NzgsIGxvIHF1ZSBzaWduaWZpY2EgcXVlIGVsIG1vZGVsbyBlc3RpbWEgcXVlIGFscmVkZWRvciBkZWwgOTklIGRlIGxhcyBwYWxhYnJhcyBlbiBlbCBkb2N1bWVudG8gOTQgc2UgZ2VuZXJhcm9uIGEgcGFydGlyIGRlbCB0ZW1hIDEuIFBvciBsbyB0YW50bywgcG9kZW1vcyBjb25jbHVpciBxdWUgZWwgdGVtYSAxIGVzIGFsdGFtZW50ZSByZWxldmFudGUgcGFyYSBlbCBkb2N1bWVudG8gOTQgc2Vnw7puIGVsIG1vZGVsby4KUG9yIG90cm8gbGFkbywgcGFyYSBlbCBkb2N1bWVudG8gNDMsIGxhIHByb2JhYmlsaWRhZCBnYW1tYSBhc29jaWFkYSBhbCB0ZW1hIDEgZXMgZGUgMC4wMDAxOCwgbG8gcXVlIGluZGljYSBxdWUgbWVub3MgZGVsIDElIGRlIGxhcyBwYWxhYnJhcyBkZWwgZG9jdW1lbnRvIDQzIHNlIGdlbmVyYW4gYSBwYXJ0aXIgZGVsIHRlbWEgMS4gRW4gZXNhIGRpcmVjY2nDs24sIHBvZGVtb3MgY29uY2x1aXIgcXVlIGVsIHRlbWEgMSBlcyBwb2NvIHJlbGV2YW50ZSBwYXJhIGVsIGRvY3VtZW50byA0MyBzZWfDum4gZWwgbW9kZWxvLgoKYGBge3J9CmNvcnB1c190aWR5X3YyJT4lCiAgZmlsdGVyKGlkPT05NCkgJT4lCiAgZ3JvdXBfYnkoaWQsIHdvcmQpICU+JQogIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgc2VsZWN0KHdvcmQsIG4pICU+JQogIGFycmFuZ2UoZGVzYyhuKSkKYGBgClNlIHZlIGNvbW8gZW4gZXN0YSBub3RpY2lhIHBhcmVjZW4gcHJlZG9taW5hciBwYWxhYnJhcyBkZWwgdMOzcGljbyAxICgibmFjaW9uYWwiLCAiZWxlY2Npb25lcyIpLiBWZWFtb3MgZWwgdGV4dG8gY29tcGxldG8gZGUgZXN0ZSBkb2N1bWVudG86CgpgYGB7cn0KY29ycHVzX2Jhc2UgJT4lCiAgZmlsdGVyKGlkPT05NCkgJT4lCiAgc2VsZWN0KHRleHRvKSAlPiUKICBwdWxsKCkKYGBgCkxhIG5vdGljaWEgKGlkPTk0KSBoYWJsYSBkZSBsYXMgZWxlY2Npb25lcyB5LCBlbiBwYXJ0aWN1bGFyLCBkZSBsYXMgZWxlY2Npb25lcyBlbiBsYSBwcm92aW5jaWEgZGUgTWVuZG96YS4KCmBgYHtyfQpkb2NfMl90b3BpY3MgJT4lCiAgcmVuYW1lKGlkID0gZG9jdW1lbnQpICU+JSAjIHRlbmVtb3MgcXVlIHJlbm9tYnJhciBsYSBjb2x1bW5hIHBhcmEgcXVlIHB1ZWRhIGhhY2Vyc2UgZWwgam9pbgogIG11dGF0ZShpZCA9IGFzLmludGVnZXIoaWQpKSAlPiUKICBsZWZ0X2pvaW4oY29ycHVzX2Jhc2UgJT4lIHNlbGVjdChpZCwgbWVkaW8pICU+JSB1bmlxdWUoKSkgJT4lCiAgZ3JvdXBfYnkobWVkaW8sIHRvcGljKSAlPiUKICAgIHN1bW1hcmlzZShtZWFuID0gbWVhbihnYW1tYSkqMTAwKSAlPiUKICBnZ3Bsb3QoKSArCiAgICBnZW9tX2NvbChhZXMoeD10b3BpYywgeT1tZWFuLCBmaWxsPW1lZGlvKSwgcG9zaXRpb249J2RvZGdlJykgKwogICAgdGhlbWVfbWluaW1hbCgpCmBgYApSZXN1bHRhZG9zIChpbnRlcnByZXRhY2nDs24gYSBkZXNhcnJvbGxhcikKRXMgcG9zaWJsZSByZWNvbm9jZXIgZGlmZXJlbmNpYXMgZW4gbGEgcHJldmFsZW5jaWEgZGUgbG9zIHTDs3BpY29zIHBhcmEgY2FkYSB1bm8gZGUgbG9zIG1lZGlvcywgY29uIGV4cGNlY2nDs24gZGVsIHTDs3BpY28gIkVjb25vbcOtYSIgcXVlIG11ZXN0cmEgdW5hIHByZXZhbGVuY2lhIHNpbWlsYXIgZW4gY3VhdHJvIG1lZGlvcy4KCjEtIFBvbMOtdGljYSBOYWNpb25hbC8gRWxlY2Npb25lcyA9IFTDqWxhbQoyLSBEZXBvcnRlcyA9IENyw7NuaWNhCjMtIFNvY2llZGFkID0gTGEgTmFjacOzbgo0LSBQb2zDrXRpY2EgSW50ZXJuYWNpbmFsID0gSW5mb2JhZQo1LSBFY29ub23DrWEgPSBUw6lsYW0sIFDDoWdpbmEgMTIsIFBlcmZpbCB5IENsYXLDrW4KCgojIyBNb2RlbGFkbyBkZSB0w7NwaWNvczogU1RNCgpQYXJhIGltcGxlbWVudGFyIHVuIG1vZGVsYWRvIGRlIHRlbWFzIGNvbiBTVE0gbmVjZXNpdGFtb3MgY29uc3RydWlyIHVuYSBtYXRyaXogREZNLgpgYGB7ciBkZm19CmRpc2NfZGZtIDwtIHdvcmRzX3RpZHlfdjIgJT4lCiAgICAgICAgICAgICAgICBjYXN0X2RmbShtZWRpbywgd29yZCwgbikKCmRpc2NfZGZtCmBgYAoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKQSBjb250aW51YWNpw7NuLCBzZWxlY2Npb25hciBsYXMgbm90aWNpYXMgdmluY3VsYWRhcyBhIGFsZ8O6biB0w7NwaWNvCnJlbGV2YW50ZSAocG9yIGVqZW1wbG8sIOKAnEVsZWNjaW9uZXPigJ0pIHkgY29uc3RydWlyIHVuIGNsYXNpZmljYWRvciBwYXJhCnByZWRlY2lyIGxhIG9yaWVudGFjacOzbiBkZWwgZGlhcmlvLiBVdGlsaXphciBhbGd1bm8gZGUgbG9zIG1vZGVsb3MgZGUKY2xhc2lmaWNhY2nDs24gdmlzdG9zIGEgbG8gbGFyZ28gZGUgYWwgRGlwbG9tYXR1cmEgKHJlZ3Jlc2nDs24gbG9nw61zdGljYSwKcmFuZG9tIGZvcmVzdCwgZXRjLikuIFV0aWxpemFyIGNvbW8gZmVhdHVyZXMgZWwg4oCcU3BhbmlzaCBCaWxsaW9uIFdvcmQKQ29ycHVzIGFuZCBFbWJlZGRpbmdz4oCdLCBhbmFsaXphZG8gZW4gY2xhc2UgKHB1ZWRlbiBkZXNjYXJnYXIgZWwKZW1iZWRkaW5nIGVuIGZvcm1hdG8gLmJpbiBkZWwgbGluaykuIMK/UXXDqSByZXN1bHRhZG9zIGFycm9qYSBlbCBtb2RlbG8/CsK/RXMgcG9zaWJsZSBtZWRpYW50ZSBlbCB0ZXh0byBkZSBsYXMgbm90aWNpYXMgY29ub2NlciBsYSBsw61uZWEgZWRpdG9yaWFsCmRlbCBkaWFyaW8/IEdlbmVyYXIgbGFzIHZpc3VhbGl6YWNpb25lcyB5IHRhYmxhcyBjb3JyZXNwb25kaWVudGVzIHBhcmEKdW5hIGNvcnJlY3RhIGV2YWx1YWNpw7NuIGRlbCBtb2RlbG8uCg==